Some Examples
Working with research data a first step is usually to read and clean the data.
We'll put that off for a little while and work with some data sets made available in R packages.
Data sets available in R packages include:
- many classic data sets;
- newer, often larger, data sets useful for learning;
- current data obtained by querying web APIs.
Old Faithful Eruptions
A simple classic data set is the geyser data frame available in package MASS
data(geyser, package = "MASS")
dim(geyser)
## [1] 299 2
head(geyser)
## waiting duration
## 1 80 4.016667
## 2 71 2.150000
## 3 57 4.000000
## 4 80 4.000000
## 5 75 4.000000
## 6 77 2.000000
head and tail return the first and last few rows of a data frame. They are useful for quick sanity checks.
The rows represent measurements recorded for eruptions of the Old Faithful geyser in Yellowstone National Park, Wyoming. The variables are:
waiting: the time in minutes since the precious eruption;
duration: the duration of the eruption.
The durations have a bimodal distribution:
ggplot(geyser) +
geom_histogram(aes(x = duration), bins = 15, color = "black", fill = "grey")

A basic template for creating a plot with ggplot:
ggplot(data = <DATA>) + <GEOM>(mapping = aes(<MAPPINGS>))
An interesting question is whether the duration can be used to predict when the next eruption will occur.
A plot of the previous duration against the waiting time to the current eruption:
ggplot(geyser) + geom_point(aes(x = lag(duration), y = waiting))
## Warning: Removed 1 rows containing missing values (geom_point).

It looks like a useful rule would be to expect a shorter waiting time after a shorter eruption.
An interesting feature: Many durations are recorded as 2 or 4 minutes. This can also be seen in a histogram with many small bins:
p <- ggplot(geyser) +
geom_histogram(aes(x = duration, y = stat(density)),
fill = "grey", color = "black", bins = 50)
p

ggplot produces a plot object. Drawing only happens when the object is printed.
Does this rounding matter?
- For many analyses it probably doesn't.
- It might if you wanted to fit normal distributions to the two groups.
Taking 3 minutes as the divide between short and long durations we can compute the means and standard deviations as
d <- geyser$duration
d_short <- d[d < 3]
d_long <- d[d >= 3]
mean(d_short)
## [1] 1.980317
sd(d_short)
## [1] 0.2779829
mean(d_long)
## [1] 4.262113
sd(d_long)
## [1] 0.3937525
mean(d >= 3)
## [1] 0.6488294
An approach that scales better is to compute group summaries using tools from the dplyr tidyverse package.
First, add a type variable:
geyser <- mutate(geyser, type = ifelse(duration < 3, "short", "long"))
The summaries can then be computed as
sgd <- summarize(group_by(geyser, type),
mean = mean(duration),
sd = sd(duration),
n = n())
(sgd <- mutate(sgd, prop = n / sum(n)))
## # A tibble: 2 x 5
## type mean sd n prop
## <chr> <dbl> <dbl> <int> <dbl>
## 1 long 4.26 0.394 194 0.649
## 2 short 1.98 0.278 105 0.351
One way to show the superimposed normal densities:
p <- p +
stat_function(color = "red",
fun = function(x)
sgd$prop[1] * dnorm(x, sgd$mean[1], sgd$sd[1])) +
stat_function(color = "blue",
fun = function(x)
sgd$prop[2] * dnorm(x, sgd$mean[2], sgd$sd[2]))
p

A ggplot can consist of several layers.
The means and standard deviations are affected by the rounding. Summaries that omit values equal to 2 or 4 minutes can be computed as
geyser2 <- filter(geyser, duration != 2, duration != 4)
sgd2 <- summarize(group_by(geyser2, type),
mean = mean(duration),
sd = sd(duration),
n = n())
(sgd2 <- mutate(sgd2, prop = n / sum(n)))
## # A tibble: 2 x 5
## type mean sd n prop
## <chr> <dbl> <dbl> <int> <dbl>
## 1 long 4.36 0.422 141 0.632
## 2 short 1.97 0.315 82 0.368
summarize, group_by, and mutate are from the dplyr package that implements a grammar of data manipulation.
A plot showing curves computed both ways:
p <- p +
stat_function(color = "red",
linetype = 2,
fun = function(x)
sgd2$prop[1] * dnorm(x, sgd2$mean[1], sgd2$sd[1])) +
stat_function(color = "blue",
linetype = 2,
fun = function(x)
sgd2$prop[2] * dnorm(x, sgd2$mean[2], sgd2$sd[2]))
p

Minnesota Barley Yields
A classic data set: Total yield in bushels per acre for 10 varieties at 6 sites in Minnesota in each of two years, 1931 and 1932.
The raw data:
data(barley, package = "lattice")
head(barley)
## yield variety year site
## 1 27.00000 Manchuria 1931 University Farm
## 2 48.86667 Manchuria 1931 Waseca
## 3 27.43334 Manchuria 1931 Morris
## 4 39.93333 Manchuria 1931 Crookston
## 5 32.96667 Manchuria 1931 Grand Rapids
## 6 28.96667 Manchuria 1931 Duluth
Some initial plots:
p1 <- ggplot(barley) + geom_point(aes(x = yield, y = variety))
p2 <- ggplot(barley) + geom_point(aes(x = yield, y = site))
cowplot::plot_grid(p1, p2)

Using color to separate yields in the two years:
p1 <- ggplot(barley) + geom_point(aes(x = yield, y = variety, color = year))
p2 <- ggplot(barley) + geom_point(aes(x = yield, y = site, color = year))
cowplot::plot_grid(p1, p2)

Can we also show site using symbol shape?
ggplot(barley) +
geom_point(aes(x = yield, y = variety, color = year, shape = site))

There is a lot of interference between shape and color.
Possible improvements:
- jittering;
- larger points.
ggplot(barley) +
geom_point(aes(x = yield, y = variety, color = year, shape = site),
position = position_jitter(height = 0.15, width = 0),
size = 2)

Another approach: faceting to produce small multiples.
ggplot(barley) +
geom_point(aes(x = yield, y = variety, color = year)) +
facet_wrap(~site)

Focusing on summaries can help. Bar charts are sometimes used for summaries, but dot plots are usually a better choice.
barley_site_year <- summarize(group_by(barley, site, year),
yield = mean(yield))
## `summarise()` has grouped output by 'site'. You can override using the `.groups` argument.
p1 <- ggplot(barley_site_year) +
geom_point(aes(y = site, x = yield, color = year), size = 3)
p2 <- ggplot(barley_site_year) +
geom_col(aes(x = site, y = yield, fill = year),
size = 3,
position = "dodge", width = .4) +
coord_flip()
cowplot::plot_grid(p1, p2)

Because of the way we perceive bars, it is important to use a zero base line for bar charts.
Hair and Eye Color Data
A data set recording the distribution of hair and eye color and sex in 592 statistics students.
The data set is available as a cross-tabulation; as.data.frame converts it to a data frame.
HairEyeDF <- as.data.frame(HairEyeColor)
head(HairEyeDF)
## Hair Eye Sex Freq
## 1 Black Brown Male 32
## 2 Brown Brown Male 53
## 3 Red Brown Male 10
## 4 Blond Brown Male 3
## 5 Black Blue Male 11
## 6 Brown Blue Male 50
Looking at the distribution of eye color:
eye <- summarize(group_by(HairEyeDF, Eye), Freq = sum(Freq))
ggplot(eye) + geom_col(aes(x = Eye, y = Freq), position = "dodge")

Mapping eye color to color in addition to the horizontal axis can help:
ggplot(eye) + geom_col(aes(x = Eye, y = Freq, fill = Eye), position = "dodge")

More sensible colors would be nice but requires a bit of work:
hazel_rgb <- col2rgb("brown") * 0.75 + col2rgb("green") * 0.25
hazel <- do.call(rgb, as.list(hazel_rgb / 255))
cols <- c(Blue = colorspace::lighten(colorspace::desaturate("blue", 0.3), 0.3),
Green = colorspace::lighten("forestgreen", 0.1),
Brown = colorspace::lighten("brown", 0.0001), ## 0.3?
Hazel = colorspace::lighten(hazel, 0.3))
pb <- ggplot(eye) +
geom_col(aes(x = Eye, y = Freq, fill = Eye), position = "dodge") +
scale_fill_manual(values = cols)
pb

A stacked bar chart can also be useful:
psb <- ggplot(eye) +
geom_col(aes(x = "", y = Freq, fill = Eye), color = "lightgrey") +
scale_fill_manual(values = cols)
psb

A pie chart can be seen as a stacked bar chart in polar coordinates:
(pp <- psb + coord_polar("y"))

The axis and grid are not helpful; a theme adjustment can remove them:
(pp <- pp + theme_void())

Themes are a way to customize the non-data components of plots: i.e. titles, labels, fonts, background, gridlines, and legends. Themes can be used to give plots a consistent customized look.
The
ggthemes package provides a number of themes to emulate the style of different publications, for example
theme_wsj and
theme_economist.
How well do bar charts and pie charts work?
cowplot::plot_grid(pb, pp)

Some questions:
Which plot makes it easier to tell whether the proportion of brown-eyed students is larger or smaller that the proportion of blue-eyed students.
Which plot makes it easier to tell whether these proportions are larger or smaller than 1/2 or 1/4 or 1/3?
Looking at the proportions within hair color and sex:
eye_hairsex <- mutate(group_by(HairEyeDF, Hair, Sex), Prop = Freq / sum(Freq))
p1 <- ggplot(eye_hairsex) +
geom_col(aes(x = Eye, y = Prop, fill = Eye)) +
scale_fill_manual(values = cols) +
facet_grid(Hair~Sex)
p2 <- ggplot(eye_hairsex) +
geom_col(aes(x = "", y = Prop, fill = Eye)) +
scale_fill_manual(values = cols) +
coord_polar("y")+facet_grid(Hair~Sex) +
theme_void()
cowplot::plot_grid(p1, p2)

A more complete ggplot template:
ggplot(data = <DATA>) +
<GEOM>(mapping = aes(<MAPPINGS>),
stat = <STAT>,
position = <POSITION>) +
< ... MORE GEOMS ... > +
<COORDINATE_ADJUSTMENT> +
<SCALE_ADJUSTMENT> +
<FACETING> +
<THEME_ADJUSTMENT>
More Examples
These examples start with raw data as you might receive it from a researcher, and involve reading and cleaning the data.
Wind Turbines in Iowa
There are many wind turbines in Iowa. Data is available from the U.S. Wind Turbine Database. A snapshot is available is here as a CSV file.
CSV files are a common form of data exchange.
They are simple text files that are intended to be written and read by a computer.
Some CSV files include a header and a footer that need to he handled.
One issue is that a comma isn't a good separator in countries where it is the decimal separator!
A CSV file can be read using read.csv or readr::read_csv.
Reading the wind turbine data:
wind_turbines <- read.csv("data/us_wind.csv", comment = "#")
Some data cleaning is needed.
Focus on the wind turbines in IOWA (19 is the FIPS county code for Iowa):
wt_IA <- filter(wind_turbines, t_fips %/% 1000 == 19)
Drop entries with missing longitude or latitude values:
wt_IA <- filter(wt_IA, ! is.na(xlong), ! is.na(ylat))
Some missing year values are encoded as -9999; replace these with NA:
wt_IA <- mutate(wt_IA, p_year = replace(p_year, p_year < 0, NA))
To show the locations of wind turbines on a map, load some map data:
iowa_sf <- sf::st_as_sf(maps::map("county", "iowa", plot = FALSE, fill = TRUE))
To show the locations of wind turbines on a map, load some map data:
iowa_sf <-
sf::st_as_sf(maps::map("county", "iowa",
plot = FALSE,
fill = TRUE))
p <- ggplot() +
geom_sf(data = iowa_sf) +
ggthemes::theme_map()
p

Locations for all wind turbines in iowa:
p + geom_point(aes(xlong, ylat), data = wt_IA)

Using color to show when the wind turbines were built:
year_brk <-c(0, 2005, 2010, 2015, 2020)
year_lab <- c("before 2005",
"2005-2009",
"2010-2014",
"2015-2020")
wt_IA <-
mutate(wt_IA,
year = cut(p_year,
breaks = year_brk,
labels = year_lab,
right = FALSE))
p + geom_point(aes(xlong,
ylat,
color = year),
data = wt_IA,
size = 3)

Cancer Map
The website http://www.cancer-rates.info/ia provides data on cancer incidence for a number of different cancers in Iowa. The data for lung and bronchus cancer in 2011 are available in a csv file in the project.
We can read the file with read_csv from the readr package.
Looking at the file shows some things that need to be cleaned up:
- Two header lines at the beginning
- Some footer lines.
- Some values codes as
~.
The header can be handled by using skip = 2 in the read_csv call:
fname <- "data/Invasive-Cancer-Incidence-Rates-by-County-in-Iowa-Lung-and-Bronchus-2011.csv"
d <- read_csv(fname, skip = 2)
## Warning: 3 parsing failures.
## row col expected actual file
## 101 -- 7 columns 1 columns 'data/Invasive-Cancer-Incidence-Rates-by-County-in-Iowa-Lung-and-Bronchus-2011.csv'
## 102 -- 7 columns 1 columns 'data/Invasive-Cancer-Incidence-Rates-by-County-in-Iowa-Lung-and-Bronchus-2011.csv'
## 103 -- 7 columns 1 columns 'data/Invasive-Cancer-Incidence-Rates-by-County-in-Iowa-Lung-and-Bronchus-2011.csv'
head(d)
## # A tibble: 6 x 7
## County `Population at … Cases `Crude Rate` `Age-adjusted R… `95% Confidence …
## <chr> <dbl> <chr> <chr> <chr> <chr>
## 1 Union 12570 20 159.11 115.82 69.86
## 2 Ringgo… 5098 11 215.77 115.03 54.48
## 3 Monroe 8044 12 149.18 109.99 56.24
## 4 Page 15926 25 156.98 109.20 70.12
## 5 Montgo… 10655 17 159.55 99.45 57.41
## 6 Adams 3996 6 150.15 95.84 35.14
## # … with 1 more variable: 95% Confidence Interval-Upper Limit <chr>
Let's focus on a few variables and give them more convenient names:
d <- select(d, county = 1, population = 2, count = 3, crude_rate = 4)
The footer needs to be removed:
tail(d)
## # A tibble: 6 x 4
## county population count crude_rate
## <chr> <dbl> <chr> <chr>
## 1 Butler 14960 5 33.42
## 2 Winneshiek 21045 ~ ~
## 3 STATE 3065223 2368 77.25
## 4 Note: All rates are per 100,000. Rates are age-ad… NA <NA> <NA>
## 5 Rates generated on Jun 12, 2019. NA <NA> <NA>
## 6 Based on data released Nov 2017. NA <NA> <NA>
One way to remove the footer:
d <- filter(d, ! is.na(population))
d <- filter(d, county != "STATE")
tail(d)
## # A tibble: 6 x 4
## county population count crude_rate
## <chr> <dbl> <chr> <chr>
## 1 Lyon 11720 ~ ~
## 2 Kossuth 15392 7 45.48
## 3 Palo Alto 9360 ~ ~
## 4 Grundy 12474 5 40.08
## 5 Butler 14960 5 33.42
## 6 Winneshiek 21045 ~ ~
Changing count and crude_rate to numeric changes the ~ entries to missing values (NA) values:
d <- mutate(d, count = as.numeric(count), crude_rate = as.numeric(crude_rate))
## Warning in mask$eval_all_mutate(quo): NAs introduced by coercion
## Warning in mask$eval_all_mutate(quo): NAs introduced by coercion
In this case there are no zero case values; two ways to check:
count(d, count == 0)
## # A tibble: 2 x 2
## `count == 0` n
## <lgl> <int>
## 1 FALSE 95
## 2 NA 4
any(d$count == 0, na.rm = TRUE)
## [1] FALSE
It might be reasonable to assume these values where zero, so replace them with zeros:
d <- replace_na(d, list(count = 0, crude_rate = 0))
A choropleth map uses color or shading to represent values measured for different geographic regions.
We will need to merge, or left join, the cancer data with the map date we loaded for the wind turbine map.
For Iowa this can be done with the county name, but some care is needed.
d$county[1]
## [1] "Union"
iowa_sf$ID[1]
## [1] "iowa,adair"
d <- mutate(d, cname = county, county = tolower(county))
iowa_sf <- mutate(iowa_sf, county = sub("iowa,", "", ID))
setdiff(d$county, iowa_sf$county)
## [1] "o'brien"
setdiff(iowa_sf$county, d$county)
## [1] "obrien"
d <- mutate(d, county = sub("'", "", county))
setdiff(d$county, iowa_sf$county)
## character(0)
setdiff(iowa_sf$county, d$county)
## character(0)
Define rate1K variable as the number of cases per 1000 inhabitants and left join the data to the polygons:
d <- mutate(d, rate1K = 1000 * (count / population))
md <- left_join(iowa_sf, d, "county")
head(md)
## Simple feature collection with 6 features and 7 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: -95.10526 ymin: 40.60552 xmax: -91.06018 ymax: 43.51041
## CRS: EPSG:4326
## ID county population count crude_rate cname rate1K
## 1 iowa,adair adair 7565 10 132.19 Adair 1.3218771
## 2 iowa,adams adams 3996 6 150.15 Adams 1.5015015
## 3 iowa,allamakee allamakee 14204 7 49.28 Allamakee 0.4928189
## 4 iowa,appanoose appanoose 12863 11 85.52 Appanoose 0.8551660
## 5 iowa,audubon audubon 6019 5 83.07 Audubon 0.8307028
## 6 iowa,benton benton 26121 28 107.19 Benton 1.0719345
## geom
## 1 MULTIPOLYGON (((-94.24583 4...
## 2 MULTIPOLYGON (((-94.70992 4...
## 3 MULTIPOLYGON (((-91.22634 4...
## 4 MULTIPOLYGON (((-92.63009 4...
## 5 MULTIPOLYGON (((-95.10526 4...
## 6 MULTIPOLYGON (((-92.06286 4...
A simple map:
library(ggthemes)
library(viridis)
## Loading required package: viridisLite
ggplot(md) + geom_sf(aes(fill = rate1K))

An improved version:
library(ggthemes)
library(viridis)
ggplot(md) +
geom_sf(aes(fill = rate1K),
color = "grey") +
scale_fill_viridis(name = "Rate per 1000") +
theme_map()

A simple interactive version using plotly:
mdl <- mutate(md,
label = paste(cname, round(rate1K, 1), population, sep = "\n"))
p <- ggplot(mdl) +
geom_sf(aes(fill = rate1K,
text = label),
color = "grey") +
scale_fill_viridis(name = "Rate per 1000") +
theme_map()
## Warning: Ignoring unknown aesthetics: text
plotly::ggplotly(p, tooltip = "text")
The
leaflet package supports more sophisticated interactive maps:
library(leaflet)
pal <- colorNumeric(
palette = "viridis",
domain = md$rate1K)
lab <- lapply(paste0(tools::toTitleCase(md$county), "<BR>",
"Rate: ", round(md$rate1K, 1), "<BR>",
"Pop: ", scales::comma(md$population,
accuracy = 1)),
htmltools::HTML)
leaflet(sf::st_transform(md, 4326)) %>%
addPolygons(weight = 2,
color = "grey",
fillColor = ~ pal(rate1K),
fillOpacity = 1,
highlightOptions =
highlightOptions(color = "white",
weight = 2,
bringToFront = TRUE),
label = lab) %>%
addLegend(pal = pal, values = ~ rate1K)
Unemployment Map
Local Area Unemployment Statistics page from the Bureau of Labor Statistics makes available county-level monthly unemployment data for a 14-month window. The file for November 2016 through December 2017 is available is available at http://www.stat.uiowa.edu/~luke/data/laus/laucntycur14-2017.txt and in the project data folder.
This file is a text file but uses a non-standard separator. It is designed for human readability and uses a comma as a thousands separator or grouping mark. It also includes header and footer information. It is still reasonably easy to read in.
One way to read the data into R is:
lausURL <- "data/laucntycur14-2017.txt"
lausUS <- read.table(lausURL,
col.names = c("LAUSAreaCode", "State", "County",
"Title", "Period",
"LaborForce", "Employed",
"Unemployed", "UnempRate"),
quote = '"', sep = "|", skip = 6,
stringsAsFactors = FALSE, strip.white = TRUE,
fill = TRUE)
footstart <- grep("------", lausUS$LAUSAreaCode)
lausUS <- lausUS[1:(footstart - 1),]
It may be useful to be able to access the county name and state name separately:
lausUS <- separate(lausUS, Title, c("cname", "scode"),
sep = ", ", fill = "right")
The UnempRate variable is read as character data because of missing value encoding, so needs to be converted to numeric:
lausUS <- mutate(lausUS, UnempRate = as.numeric(UnempRate))
## Warning in mask$eval_all_mutate(quo): NAs introduced by coercion
Check for missing values:
sapply(lausUS, function(x) any(is.na(x)))
## LAUSAreaCode State County cname scode Period
## FALSE FALSE FALSE FALSE TRUE FALSE
## LaborForce Employed Unemployed UnempRate
## FALSE FALSE FALSE TRUE
The state code is missing for the District of Columbia:
unique(filter(select(lausUS, cname, scode), is.na(scode)))
## cname scode
## 1 District of Columbia <NA>
Missing values for UnempRate are all for Puerto Rico and September 2017. Hurricane Maria made landfall on September 20.
unique(filter(select(lausUS, scode, Period, UnempRate), is.na(UnempRate)))
## scode Period UnempRate
## 1 PR Sep-17 NA
Average unemployment rates over the period can be computed as
avgUS <- summarize(group_by(lausUS, County, State),
avg_unemp = mean(UnempRate),
cname = unique(cname),
scode = unique(scode))
## `summarise()` has grouped output by 'County'. You can override using the `.groups` argument.
head(avgUS)
## # A tibble: 6 x 5
## # Groups: County [1]
## County State avg_unemp cname scode
## <int> <int> <dbl> <chr> <chr>
## 1 1 1 4.21 Autauga County AL
## 2 1 4 10.5 Apache County AZ
## 3 1 5 3.18 Arkansas County AR
## 4 1 6 3.72 Alameda County CA
## 5 1 8 2.88 Adams County CO
## 6 1 9 4.41 Fairfield County CT
To show average unemployment rates on a map we need to merge the unemployment data with map data.
It is safer to use the numeric FIPS county code, which can be computed as
Add FIPS code to avgUS:
avgUS <- mutate(avgUS, fips = 1000 * State + County)
head(avgUS)
## # A tibble: 6 x 6
## # Groups: County [1]
## County State avg_unemp cname scode fips
## <int> <int> <dbl> <chr> <chr> <dbl>
## 1 1 1 4.21 Autauga County AL 1001
## 2 1 4 10.5 Apache County AZ 4001
## 3 1 5 3.18 Arkansas County AR 5001
## 4 1 6 3.72 Alameda County CA 6001
## 5 1 8 2.88 Adams County CO 8001
## 6 1 9 4.41 Fairfield County CT 9001
The county.fips data frame in the maps package links the FIPS code to region names used by the map data in the maps package.
library(maps)
##
## Attaching package: 'maps'
## The following object is masked from 'package:purrr':
##
## map
head(county.fips)
## fips polyname
## 1 1001 alabama,autauga
## 2 1003 alabama,baldwin
## 3 1005 alabama,barbour
## 4 1007 alabama,bibb
## 5 1009 alabama,blount
## 6 1011 alabama,bullock
Some issues:
filter(county.fips, grepl("florida,o", polyname))
## fips polyname
## 1 12091 florida,okaloosa:main
## 2 12091 florida,okaloosa:spit
## 3 12093 florida,okeechobee
## 4 12095 florida,orange
## 5 12097 florida,osceola
head(select(filter(lausUS, scode == "LA"), cname))
## cname
## 1 Acadia Parish
## 2 Allen Parish
## 3 Ascension Parish
## 4 Assumption Parish
## 5 Avoyelles Parish
## 6 Beauregard Parish
Cleaning up a bit:
county.fips <- separate(county.fips, polyname,
c("state", "county", "part"),
sep = "[,:]", fill = "right")
head(county.fips)
## fips state county part
## 1 1001 alabama autauga <NA>
## 2 1003 alabama baldwin <NA>
## 3 1005 alabama barbour <NA>
## 4 1007 alabama bibb <NA>
## 5 1009 alabama blount <NA>
## 6 1011 alabama bullock <NA>
County map data for the lower 48 states:
counties_US <- map_data("county")
counties_US <- rename(counties_US, state = region, county = subregion)
counties_US <- left_join(counties_US, county.fips, c("state", "county"))
A choropleth map:
ggplot(left_join(counties_US, avgUS, "fips")) +
geom_polygon(aes(x = long, y = lat, fill = avg_unemp, group = group)) +
scale_fill_viridis(name = "Rate", na.value = "red") +
theme_map() + coord_map() +
geom_polygon(aes(x = long, y = lat, group = group),
data = map_data("state"), col = "grey", fill = NA)

A version using geom_map leaves out Shannon County, SD (46113) which for some reason is not in the LAUS data.
ggplot(avgUS, aes(fill = avg_unemp, map_id = fips)) +
geom_map(map = mutate(counties_US, id = fips)) +
with(counties_US, expand_limits(x = long, y = lat)) +
scale_fill_viridis(name = "Rate", na.value = "red") +
theme_map() + coord_map()

Gapminder Childhood Mortality Data
The gapminder package provides a subset of the data from the Gapminder web site. Additional data sets are available.
A data set on childhood mortality is available locally as a csv file or an Excel file. The Excel file is also available in the project data folder.
The numbers represent number of deaths within the first five years per 1000 births.
Many researchers like to manage their data in a spreadsheet. Being able to read such a sheet directly greatly helps keeping the workflow reproducible.
Many spreadsheets contain header, footers, and other annotations to aid a human viewer.
As long as the data are in a rectangular region it is usually not hard to extract them programmatically.
Loading the data:
library(readxl)
gcm <- read_excel("data/gapminder-under5mortality.xlsx")
names(gcm)[1]
## [1] "Under five mortality"
names(gcm)[1] <- "country"
This data set is in wide format.
A long version is useful for working with ggplot.
tgcm <- gather(gcm, year, u5mort, -1)
head(tgcm)
## # A tibble: 6 x 3
## country year u5mort
## <chr> <chr> <dbl>
## 1 Abkhazia 1800.0 NA
## 2 Afghanistan 1800.0 469.
## 3 Akrotiri and Dhekelia 1800.0 NA
## 4 Albania 1800.0 375.
## 5 Algeria 1800.0 460.
## 6 American Samoa 1800.0 NA
tgcm <- mutate(tgcm, year = as.numeric(year))
head(tgcm)
## # A tibble: 6 x 3
## country year u5mort
## <chr> <dbl> <dbl>
## 1 Abkhazia 1800 NA
## 2 Afghanistan 1800 469.
## 3 Akrotiri and Dhekelia 1800 NA
## 4 Albania 1800 375.
## 5 Algeria 1800 460.
## 6 American Samoa 1800 NA
Some explorations:
library(lattice)
## Warning: package 'lattice' was built under R version 4.0.5
p <- ggplot(tgcm) + geom_line(aes(year, u5mort, group = country), alpha = 0.3)
p
## Warning: Removed 18644 row(s) containing missing values (geom_path).

plotly::ggplotly(p)
Some selected countries:
countries <- c("United States", "United Kingdom", "Germany", "China", "Egypt")
tcgm1 <- filter(tgcm, country %in% countries)
ggplot(tcgm1) + geom_line(aes(x = year, y = u5mort, color = country))

Examining the missing values:
tgcm_miss <- summarize(group_by(tgcm, country), anyNA = any(is.na(u5mort)))
tgcm_miss <- filter(tgcm_miss, anyNA)$country
p <- ggplot(filter(tgcm, country %in% tgcm_miss)) +
geom_line(aes(x = year, y = u5mort, group = country), na.rm = TRUE)
p

plotly::ggplotly(p)
LS0tCnRpdGxlOiAiQmFzaWMgRGF0YSBXcmFuZ2xpbmcgYW5kIERhdGEgVmlzdWFsaXphdGlvbiBpbiBSIgphdXRob3I6ICJMdWtlIFRpZXJuZXkiCmRhdGU6ICIyMSBKdW5lLCAyMDIxIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKYGBge3IgZ2xvYmFsX29wdGlvbnMsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChjb2xsYXBzZT1UUlVFKQpgYGAKCiMjIEludHJvZHVjdGlvbgoKSW4gdGhpcyBjbGFzcyBJIHdpbGwKCi0gQnJpZWZseSBvdXRsaW5lIHRoZSBoaXN0b3J5IG9mIFIuCi0gVXNpbmcgc29tZSBleGFtcGxlcyBicmllZmx5IHNob3cgaG93IHRvIGRvIGRhdGEgd3JhbmdsaW5nCiAgYW5kIHZpc3VhbGl6ZSBkYXRhIGluIFIuCiAKTWF0ZXJpYWxzIGZvciB0aGlzIGNsYXNzIGFyZSBhdmFpbGFibGUgb24gR2l0SHViIGF0CjxodHRwczovL2dpdGh1Yi5jb20vbHRpZXJuZXkvU0lCUy1XVi0yMDIxLmdpdD4uCgoqIFlvdSBjYW4gYWNjZXNzIGl0IGFzIGFuIFJTdHVkaW8gcHJvamVjdCBieSBmb2xsb3dpbmcgdGhlIG1lbnUgc2VsZWN0aW9uCiAgKipGaWxlID4gTmV3IFByb2plY3QgPiBWZXJzaW9uIENvbnRyb2wgPiBHaXQqKiBhbmQgc3BlY2lmeWluZyB0aGlzIFVSTC4KKiBZb3UgY2FuIHVzZSB0aGUgYGdpdGAgY29tbWFuZCBsaW5lIGNsaWVudCB3aXRoCiAgICBgYGBzaGVsbApnaXQgY2xvbmUgaHR0cHM6Ly9naXRodWIuY29tL2x0aWVybmV5L1NJQlMtV1YtMjAyMS5naXQKICAgIGBgYAoKTWF0ZXJpYWxzIGZvciBvdXIgX0RhdGEgVmlzdWFsaXphdGlvbiBhbmQgRGF0YQpUZWNobm9sb2dpZXNfIGNvdXJzZSBhcmUgYXZhaWxhYmxlIGF0CiA8aHR0cDovL3d3dy5zdGF0LnVpb3dhLmVkdS9+bHVrZS9jbGFzc2VzL1NUQVQ0NTgwLTIwMjEvPgoKU29tZSB0b29scyBJIHdpbGwgYmUgdXNpbmc6CgoqIFRoZSBbUlN0dWRpb10oaHR0cHM6Ly93d3cucnN0dWRpb24uY29tKSBJREUuCiogTWFueSBmZWF0dXJlcyBmcm9tIHRoZSBiYXNpYyBbUl0oaHR0cHM6Ly93d3cuci1wcm9qZWN0Lm9yZykgZGlzdHJpYnV0aW9uLgoqIFNvbWUgdG9vbHMgZnJvbSB0aGUgW190aWR5dmVyc2VfXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykuCiogVGhlIFtgZ2dwbG90YF0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvKSBwYWNrYWdlIGJhc2VkIG9uCiAgdGhlIF9HcmFtbWFyIG9mIEdyYXBoaWNzXyBmcmFtZXdvcmsuCgpNb3N0IG9mIHRoZSBwYWNrYWdlcyBhcmUgbG9hZGVkIGJ5IGxvYWRpbmcgdGhlIGB0aWR5dmVyc2VgIHBhY2thZ2UKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKClVzZWZ1bCByZWZlcmVuY2VzOgoKPiBIYWRsZXkgV2lja2hhbSBhbmQgR2FycmV0dCBHcm9sZW11bmQgKDIwMTYpLCBbX1IgZm9yIERhdGEKPiBTY2llbmNlX10oaHR0cDovL3I0ZHMuaGFkLmNvLm56LyksIE8nUmVpbGx5LgoKPiBDbGF1cyBPLiBXaWxrZSAoMjAxOSksIFtfRnVuZGFtZW50YWxzIG9mIERhdGEKPiAgVmlzdWFsaXphdGlvbl9dKGh0dHBzOi8vc2VyaWFsbWVudG9yLmNvbS9kYXRhdml6LyksIE8nUmVpbGx5LgogIApBc2sgcXVlc3Rpb25zIGFueSB0aW1lIQoKIAojIyMgVGhlIFIgTGFuZ3VhZ2UKClIgaXMgYSBsYW5ndWFnZSBmb3IgZGF0YSBhbmFseXNpcyBhbmQgZ3JhcGhpY3MuCgoqIFIgd2FzIG9yaWdpbmFsbHkgZGV2ZWxvcGVkIGJ5IFJvYmVydCBHZW50bGVtYW4gYW5kIFJvc3MgSWhha2EgaW4gdGhlCiAgZWFybHkgMTk5MCdzIGZvciBhIE1hY2ludG9zaCBjb21wdXRlciBsYWIgYXQgVS4gb2YgQXVja2xhbmQsIE5ldyBaZWFsYW5kLgoqIFIgaXMgYmFzZWQgb24gdGhlIFMgbGFuZ3VhZ2UgZGV2ZWxvcGVkIGJ5IEpvaG4gQ2hhbWJlcnMgYW5kCiAgb3RoZXJzIGF0IEJlbGwgTGFicy4KClIgaXMgYW4gT3BlbiBTb3VyY2UgcHJvamVjdC4KCiogU2luY2UgMTk5NyBSIGlzIGRldmVsb3BlZCBhbmQgbWFpbnRhaW5lZCBieSB0aGUgUi1jb3JlIGdyb3VwLAogIHdpdGggYXJvdW5kIDIwIG1lbWJlcnMgbG9jYXRlZCBpbiBtYW9yIHRoYW4gMTAgZGlmZmVyZW50IGNvdW50cmllcy4KCiogUiBpcyB3aWRlbHkgdXNlZCBpbiB0aGUgZmllbGQgb2Ygc3RhdGlzdGljcyBhbmQgYmV5b25kLCBlc3BlY2lhbGx5IGluCiAgdW5pdmVyc2l0eSBlbnZpcm9ubWVudHMuCgoqIFIgaGFzIGJlY29tZSB0aGUgcHJpbWFyeSBmcmFtZXdvcmsgZm9yIGRldmVsb3BpbmcgYW5kIG1ha2luZyBhdmFpbGFibGUKICBuZXcgc3RhdGlzdGljYWwgbWV0aG9kb2xvZ3kuCgoqIE1hbnkgKG5vdyBvdmVyIDE3LDAwMCkgZXh0ZW5zaW9uIHBhY2thZ2VzIGFyZSBhdmFpbGFibGUgdGhyb3VnaCBDUkFOIG9yCiAgc2ltaWxhciByZXBvc2l0b3JpZXMuCgojIyMgV29ya2luZyB3aXRoIFIKClIgaXMgZGVzaWduZWQgZm9yIGludGVyYWN0aXZlIGRhdGEgZXhwbG9yYXRpb24uCgoqIEludGVyYWN0aW9uIGlzIHRocm91Z2ggYSBfcmVhZC1ldmFsLXByaW50IGxvb3AgKFJFUEwpXy4KKiBUaGlzIGlzIGFsc28gY2FsbGVkIGEgX2NvbW1hbmQgbGluZSBpbnRlcmZhY2UgKENMSSlfLgoKQWxsIGNvbXB1dGF0aW9ucyBhcmUgc3BlY2lmaWVkIGluIHRoZSBSIGxhbmd1YWdlLgoKKiBFdmVuIGZvciBzaW1wbGUgdGFza3MgeW91IG5lZWQgdG8ga25vdyBhIGxpdHRsZSBvZiB0aGUgbGFuZ3VhZ2UuCiogQWZ0ZXIgbGVhcm5pbmcgdG8gZG8gc2ltcGxlIHRhc2tzIHlvdSBrbm93IHNvbWUgb2YgdGhlIGxhbmd1YWdlLgoKVGhlIGxhbmd1YWdlIGlzIHVzZWQgdG8KICAgIAoqIHByZXBhcmUgZGF0YSBmb3IgYW5hbHlzaXM7Ciogc3BlY2lmeSBpbmRpdmlkdWFsIGFuYWx5c2VzOwoqIHByb2dyYW0gcmVwZWF0ZWQgb3Igc2ltaWxhciBhbmFseXNlczsKKiBwcm9ncmFtIG5ldyBtZXRob2RzIG9mIGFuYWx5c2lzLgoKU3BlY2lmeWluZyB0aGVzZSB0YXNrcyBpbiBhIGxhbmd1YWdlIHN1cHBvcnRzIF9yZXByb2R1Y2libGUgcmVzZWFyY2hfLgoKVGhlIFIgbGFuZ3VhZ2Ugb3BlcmF0ZXMgb24gdmVjdG9ycyBhbmQgYXJyYXlzLgoKQ29tbW9ubHkgdXNlZCBkYXRhIHR5cGVzIGFyZToKCiogaW50ZWdlciBhbmQgbnVtZXJpYyB2ZWN0b3JzOwoqIGxvZ2ljYWwgdmVjdG9yczsKKiBjaGFyYWN0ZXIgdmVjdG9yczsKKiBmYWN0b3JzLgoKQWxsIGJhc2ljIHZlY3RvciB0eXBlcyBzdXBwb3J0IG1pc3NpbmcgKGBOQWApIHZhbHVlcy4KCkFyaXRobWV0aWMgb3BlcmF0aW9ucyBhcmUgdmVjdG9yaXplZCB0byBvcGVyYXRlIGVsZW1lbnQtd2lzZSBvbiB2ZWN0b3JzLgoKRGF0YSB2ZWN0b3JzIGFyZSB1c3VhbGx5IGNvbWJpbmVkIGludG8gdGFibGUtbGlrZSBvYmplY3RzIGNhbGxlZCBfZGF0YQpmcmFtZXNfLgoKCiMjIyBUaGUgRGF0YSBBbmFseXNpcyBQcm9jZXNzCgpBIGZpZ3VyZSB0aGF0IHNob3dzIHRoZSBzdGVwcyB1c3VhbGx5IGludm9sdmVkIGluIGEgZGF0YSBhbmFseXNpcwpwcm9qZWN0OgoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0KbGlicmFyeShub21ub21sKQpgYGAKPGNlbnRlcj4KYGBge25vbW5vbWwsIGVjaG8gPSBGQUxTRX0KI3BhZGRpbmc6IDI1CiNmb250c2l6ZTogMTgKI2ZpbGw6ICNFMURBRkY7ICNENEE5RkYKI3N0cm9rZTogIzg1MTVDNwojbGluZXdpZHRoOiAyCgpbSW1wb3J0XSAtPiBbVW5kZXJzdGFuZF0KW1VuZGVyc3RhbmQgfAogIFtXcmFuZ2xlXSAtPiBbVmlzdWFsaXplXQogIFtWaXN1YWxpemVdIC0+IFtNb2RlbF0KICBbTW9kZWxdIC0+IFtXcmFuZ2xlXQpdCltVbmRlcnN0YW5kXSAtPiBbQ29tbXVuaWNhdGVdCmBgYAo8L2NlbnRlcj4KClNhdmluZyB3b3JrIGluIGEgdGV4dCBmaWxlIG9yIGEgbm90ZWJvb2sgYW5kIHRyYWNraW5nIGNoYW5nZXMgdG8KeW91ciBmaWxlcyB3aXRoIGEgdmVyc2lvbiBjb250cm9sIHN5c3RlbSBsaWtlCltgZ2l0YF0oaHR0cHM6Ly9naXQtc2NtLmNvbS8pIHdpbGwgYWxsb3cgeW91IHRvIHlvdSBvciBzb21lb25lIGVsc2UKcmVwcm9kdWNlIHlvdXIgcmVzdWx0cy4KClVzaW5nIGEgc3lzdGVtIGxpa2UgUm1hcmtkb3duIHRvIHByZXBhcmUgeW91ciByZXBvcnQgYXZvaWRzIHRoZSByaXNrcwpvZiBjdXR0aW5nIGFuZCBwYXN0aW5nIHJlc3VsdHMgYW5kIGFsbG93cyB5b3UgdG8gcmUtY3JlYXRlIHlvdXIgcmVwb3J0CndoZW4gZGF0YSBjaGFuZ2VzIChhcyBpdCBvZnRlbiB3aWxsISkKCkEgZ29vZCByZXNvdXJjZSBmb3Igc2V0dGluZyB1cCB5b3VyIHRvb2xzIHRvIHN1cHBvcnQgdGhpcyBpcyBbX0hhcHB5CkdpdCBhbmQgR2l0SHViIGZvciB0aGUgdXNlUl9dKGh0dHBzOi8vaGFwcHlnaXR3aXRoci5jb20vKS4KCgojIyBTb21lIEV4YW1wbGVzCgpXb3JraW5nIHdpdGggcmVzZWFyY2ggZGF0YSBhIGZpcnN0IHN0ZXAgaXMgdXN1YWxseSB0byByZWFkIGFuZCBjbGVhbgp0aGUgZGF0YS4KCldlJ2xsIHB1dCB0aGF0IG9mZiBmb3IgYSBsaXR0bGUgd2hpbGUgYW5kIHdvcmsgd2l0aCBzb21lIGRhdGEgc2V0cwptYWRlIGF2YWlsYWJsZSBpbiBSIHBhY2thZ2VzLgoKRGF0YSBzZXRzIGF2YWlsYWJsZSBpbiBSIHBhY2thZ2VzIGluY2x1ZGU6CgoqIG1hbnkgY2xhc3NpYyBkYXRhIHNldHM7CiogbmV3ZXIsIG9mdGVuIGxhcmdlciwgZGF0YSBzZXRzIHVzZWZ1bCBmb3IgbGVhcm5pbmc7CiogY3VycmVudCBkYXRhIG9idGFpbmVkIGJ5IHF1ZXJ5aW5nIHdlYiBBUElzLgoKCiMjIyBPbGQgRmFpdGhmdWwgRXJ1cHRpb25zCgpBIHNpbXBsZSBjbGFzc2ljIGRhdGEgc2V0IGlzIHRoZSBgZ2V5c2VyYCBkYXRhIGZyYW1lIGF2YWlsYWJsZSBpbgpwYWNrYWdlIGBNQVNTYAoKYGBge3J9CmRhdGEoZ2V5c2VyLCBwYWNrYWdlID0gIk1BU1MiKQpkaW0oZ2V5c2VyKQpoZWFkKGdleXNlcikKYGBgCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4gYGhlYWRgIGFuZCBgdGFpbGAgcmV0dXJuIHRoZSBmaXJzdCBhbmQKbGFzdCBmZXcgcm93cyBvZiBhIGRhdGEgZnJhbWUuIFRoZXkgYXJlIHVzZWZ1bCBmb3IgcXVpY2sgc2FuaXR5CmNoZWNrcy4gIDwvZGl2PgoKVGhlIHJvd3MgcmVwcmVzZW50IG1lYXN1cmVtZW50cyByZWNvcmRlZCBmb3IgZXJ1cHRpb25zIG9mIHRoZSBfT2xkCkZhaXRoZnVsXyBnZXlzZXIgaW4gWWVsbG93c3RvbmUgTmF0aW9uYWwgUGFyaywgV3lvbWluZy4gVGhlIHZhcmlhYmxlcwphcmU6CgoqIGB3YWl0aW5nYDogdGhlIHRpbWUgaW4gbWludXRlcyBzaW5jZSB0aGUgcHJlY2lvdXMgZXJ1cHRpb247CiogYGR1cmF0aW9uYDogdGhlIGR1cmF0aW9uIG9mIHRoZSBlcnVwdGlvbi4KClRoZSBkdXJhdGlvbnMgaGF2ZSBhIGJpbW9kYWwgZGlzdHJpYnV0aW9uOgoKYGBge3J9CmdncGxvdChnZXlzZXIpICsKICAgIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gZHVyYXRpb24pLCBiaW5zID0gMTUsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJncmV5IikKYGBgCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4gQSBiYXNpYyB0ZW1wbGF0ZSBmb3IgY3JlYXRpbmcgYSBwbG90CndpdGggYGdncGxvdGA6CgpgYGByCmdncGxvdChkYXRhID0gPERBVEE+KSArIDxHRU9NPihtYXBwaW5nID0gYWVzKDxNQVBQSU5HUz4pKQpgYGAKPC9kaXY+CgpBbiBpbnRlcmVzdGluZyBxdWVzdGlvbiBpcyB3aGV0aGVyIHRoZSBkdXJhdGlvbiBjYW4gYmUgdXNlZCB0byBwcmVkaWN0CndoZW4gdGhlIF9uZXh0XyBlcnVwdGlvbiB3aWxsIG9jY3VyLgoKQSBwbG90IG9mIHRoZSBfcHJldmlvdXNfIGR1cmF0aW9uIGFnYWluc3QgdGhlIHdhaXRpbmcgdGltZSB0byB0aGUKY3VycmVudCBlcnVwdGlvbjoKCmBgYHtyfQpnZ3Bsb3QoZ2V5c2VyKSArIGdlb21fcG9pbnQoYWVzKHggPSBsYWcoZHVyYXRpb24pLCB5ID0gd2FpdGluZykpCmBgYAoKSXQgbG9va3MgbGlrZSBhIHVzZWZ1bCBydWxlIHdvdWxkIGJlIHRvIGV4cGVjdCBhIHNob3J0ZXIgd2FpdGluZyB0aW1lCmFmdGVyIGEgc2hvcnRlciBlcnVwdGlvbi4KCkFuIGludGVyZXN0aW5nIGZlYXR1cmU6IE1hbnkgZHVyYXRpb25zIGFyZSByZWNvcmRlZCBhcyAyIG9yIDQgbWludXRlcy4KVGhpcyBjYW4gYWxzbyBiZSBzZWVuIGluIGEgaGlzdG9ncmFtIHdpdGggbWFueSBzbWFsbCBiaW5zOgoKYGBge3J9CnAgPC0gZ2dwbG90KGdleXNlcikgKwogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBkdXJhdGlvbiwgeSA9IHN0YXQoZGVuc2l0eSkpLAogICAgICAgICAgICAgICAgICAgZmlsbCA9ICJncmV5IiwgY29sb3IgPSAiYmxhY2siLCBiaW5zID0gNTApCnAKYGBgCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4gYGdncGxvdGAgcHJvZHVjZXMgYSBwbG90Cm9iamVjdC4gRHJhd2luZyBvbmx5IGhhcHBlbnMgd2hlbiB0aGUgb2JqZWN0IGlzIHByaW50ZWQuICA8L2Rpdj4KCkRvZXMgdGhpcyByb3VuZGluZyBtYXR0ZXI/CgoqIEZvciBtYW55IGFuYWx5c2VzIGl0IHByb2JhYmx5IGRvZXNuJ3QuCiogSXQgbWlnaHQgaWYgeW91IHdhbnRlZCB0byBmaXQgbm9ybWFsIGRpc3RyaWJ1dGlvbnMgdG8gdGhlIHR3byBncm91cHMuCgpUYWtpbmcgMyBtaW51dGVzIGFzIHRoZSBkaXZpZGUgYmV0d2VlbiBzaG9ydCBhbmQgbG9uZyBkdXJhdGlvbnMgd2UgY2FuCmNvbXB1dGUgdGhlIG1lYW5zIGFuZCBzdGFuZGFyZCBkZXZpYXRpb25zIGFzCgpgYGB7cn0KZCA8LSBnZXlzZXIkZHVyYXRpb24KZF9zaG9ydCA8LSBkW2QgPCAzXQpkX2xvbmcgPC0gZFtkID49IDNdCm1lYW4oZF9zaG9ydCkKc2QoZF9zaG9ydCkKbWVhbihkX2xvbmcpCnNkKGRfbG9uZykKbWVhbihkID49IDMpCmBgYAoKQW4gYXBwcm9hY2ggdGhhdCBzY2FsZXMgYmV0dGVyIGlzIHRvIGNvbXB1dGUgZ3JvdXAgc3VtbWFyaWVzIHVzaW5nCnRvb2xzIGZyb20gdGhlIGBkcGx5cmAgYHRpZHl2ZXJzZWAgcGFja2FnZS4KCkZpcnN0LCBhZGQgYSBgdHlwZWAgdmFyaWFibGU6CgpgYGB7cn0KZ2V5c2VyIDwtIG11dGF0ZShnZXlzZXIsIHR5cGUgPSBpZmVsc2UoZHVyYXRpb24gPCAzLCAic2hvcnQiLCAibG9uZyIpKQpgYGAKClRoZSBzdW1tYXJpZXMgY2FuIHRoZW4gYmUgY29tcHV0ZWQgYXMKCmBgYHtyfQpzZ2QgPC0gc3VtbWFyaXplKGdyb3VwX2J5KGdleXNlciwgdHlwZSksCiAgICAgICAgICAgICAgICAgbWVhbiA9IG1lYW4oZHVyYXRpb24pLAogICAgICAgICAgICAgICAgIHNkID0gc2QoZHVyYXRpb24pLAogICAgICAgICAgICAgICAgIG4gPSBuKCkpCihzZ2QgPC0gbXV0YXRlKHNnZCwgcHJvcCA9IG4gLyBzdW0obikpKQpgYGAKCk9uZSB3YXkgdG8gc2hvdyB0aGUgc3VwZXJpbXBvc2VkIG5vcm1hbCBkZW5zaXRpZXM6CgpgYGB7cn0KcCA8LSBwICsKICAgIHN0YXRfZnVuY3Rpb24oY29sb3IgPSAicmVkIiwKICAgICAgICAgICAgICAgICAgZnVuID0gZnVuY3Rpb24oeCkKICAgICAgICAgICAgICAgICAgICAgICAgICBzZ2QkcHJvcFsxXSAqIGRub3JtKHgsIHNnZCRtZWFuWzFdLCBzZ2Qkc2RbMV0pKSArCiAgICBzdGF0X2Z1bmN0aW9uKGNvbG9yID0gImJsdWUiLAogICAgICAgICAgICAgICAgICBmdW4gPSBmdW5jdGlvbih4KQogICAgICAgICAgICAgICAgICAgICAgICAgIHNnZCRwcm9wWzJdICogZG5vcm0oeCwgc2dkJG1lYW5bMl0sIHNnZCRzZFsyXSkpCnAKYGBgCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4gQSBgZ2dwbG90YCBjYW4gY29uc2lzdCBvZiBzZXZlcmFsCl9sYXllcnNfLiAgPC9kaXY+CgpUaGUgbWVhbnMgYW5kIHN0YW5kYXJkIGRldmlhdGlvbnMgYXJlIGFmZmVjdGVkIGJ5IHRoZQpyb3VuZGluZy4gU3VtbWFyaWVzIHRoYXQgb21pdCB2YWx1ZXMgZXF1YWwgdG8gMiBvciA0IG1pbnV0ZXMgY2FuIGJlCmNvbXB1dGVkIGFzCgpgYGB7cn0KZ2V5c2VyMiA8LSBmaWx0ZXIoZ2V5c2VyLCBkdXJhdGlvbiAhPSAyLCBkdXJhdGlvbiAhPSA0KQpzZ2QyIDwtIHN1bW1hcml6ZShncm91cF9ieShnZXlzZXIyLCB0eXBlKSwKICAgICAgICAgICAgICAgICAgbWVhbiA9IG1lYW4oZHVyYXRpb24pLAogICAgICAgICAgICAgICAgICBzZCA9IHNkKGR1cmF0aW9uKSwKICAgICAgICAgICAgICAgICAgbiA9IG4oKSkKKHNnZDIgPC0gbXV0YXRlKHNnZDIsIHByb3AgPSBuIC8gc3VtKG4pKSkKYGBgCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4gYHN1bW1hcml6ZWAsIGBncm91cF9ieWAsIGFuZCBgbXV0YXRlYAphcmUgZnJvbSB0aGUgYGRwbHlyYCBwYWNrYWdlIHRoYXQgaW1wbGVtZW50cyBhIF9ncmFtbWFyIG9mIGRhdGEKbWFuaXB1bGF0aW9uXy4gIDwvZGl2PgoKQSBwbG90IHNob3dpbmcgY3VydmVzIGNvbXB1dGVkIGJvdGggd2F5czoKCmBgYHtyfQpwIDwtIHAgKwogICAgc3RhdF9mdW5jdGlvbihjb2xvciA9ICJyZWQiLAogICAgICAgICAgICAgICAgICBsaW5ldHlwZSA9IDIsCiAgICAgICAgICAgICAgICAgIGZ1biA9IGZ1bmN0aW9uKHgpCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2dkMiRwcm9wWzFdICogZG5vcm0oeCwgc2dkMiRtZWFuWzFdLCBzZ2QyJHNkWzFdKSkgKwogICAgc3RhdF9mdW5jdGlvbihjb2xvciA9ICJibHVlIiwKICAgICAgICAgICAgICAgICAgbGluZXR5cGUgPSAyLAogICAgICAgICAgICAgICAgICBmdW4gPSBmdW5jdGlvbih4KQogICAgICAgICAgICAgICAgICAgICAgICAgIHNnZDIkcHJvcFsyXSAqIGRub3JtKHgsIHNnZDIkbWVhblsyXSwgc2dkMiRzZFsyXSkpCnAKYGBgCgpgYGB7ciwgZXZhbCA9IEZBTFNFLCBlY2hvID0gRkFMU0V9CiMjIEZhbmNpZXIgdmVyc2lvbiB0aGF0IGdldHMgYSBjb2xvciBsZWdlbmQuCiMjIENvdWxkIGFsc28gZ2V0IGEgbGluZSB0eXBlIGxlZ2VuZC4KcCA8LSBnZ3Bsb3QoZ2V5c2VyKSArCiAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IGR1cmF0aW9uLCB5ID0gc3RhdChkZW5zaXR5KSksCiAgICAgICAgICAgICAgICAgICBmaWxsID0gImdyZXkiLCBjb2xvciA9ICJibGFjayIsIGJpbnMgPSA1MCkKcCA8LSBwICsgCiAgICBzdGF0X2Z1bmN0aW9uKGFlcyhjb2xvciA9IHR5cGUpLAogICAgICAgICAgICAgICAgICBkYXRhID0gZmlsdGVyKHNnZCwgdHlwZSA9PSAibG9uZyIpLAogICAgICAgICAgICAgICAgICBmdW4gPSBmdW5jdGlvbih4KQogICAgICAgICAgICAgICAgICAgICAgICAgIHNnZCRwcm9wWzFdICogZG5vcm0oeCwgc2dkJG1lYW5bMV0sIHNnZCRzZFsxXSkpICsKICAgIHN0YXRfZnVuY3Rpb24oYWVzKGNvbG9yID0gdHlwZSksCiAgICAgICAgICAgICAgICAgIGRhdGEgPSBmaWx0ZXIoc2dkLCB0eXBlID09ICJzaG9ydCIpLAogICAgICAgICAgICAgICAgICBmdW4gPSBmdW5jdGlvbih4KQogICAgICAgICAgICAgICAgICAgICAgICAgIHNnZCRwcm9wWzJdICogZG5vcm0oeCwgc2dkJG1lYW5bMl0sIHNnZCRzZFsyXSkpCnAKCnAgPC0gcCArCiAgICAgc3RhdF9mdW5jdGlvbihhZXMoY29sb3IgPSB0eXBlKSwKICAgICAgICAgICAgICAgICAgZGF0YSA9IGZpbHRlcihzZ2QyLCB0eXBlID09ICJsb25nIiksCiAgICAgICAgICAgICAgICAgIGxpbmV0eXBlID0gMiwKICAgICAgICAgICAgICAgICAgZnVuID0gZnVuY3Rpb24oeCkKICAgICAgICAgICAgICAgICAgICAgICAgICBzZ2QyJHByb3BbMV0gKiBkbm9ybSh4LCBzZ2QyJG1lYW5bMV0sIHNnZDIkc2RbMV0pKSArCiAgICBzdGF0X2Z1bmN0aW9uKGFlcyhjb2xvciA9IHR5cGUpLAogICAgICAgICAgICAgICAgICBkYXRhID0gZmlsdGVyKHNnZDIsIHR5cGUgPT0gInNob3J0IiksCiAgICAgICAgICAgICAgICAgIGxpbmV0eXBlID0gMiwKICAgICAgICAgICAgICAgICAgZnVuID0gZnVuY3Rpb24oeCkKICAgICAgICAgICAgICAgICAgICAgICAgICBzZ2QyJHByb3BbMl0gKiBkbm9ybSh4LCBzZ2QyJG1lYW5bMl0sIHNnZDIkc2RbMl0pKQpwCmBgYAoKCiMjIyBNaW5uZXNvdGEgQmFybGV5IFlpZWxkcwoKQSBjbGFzc2ljIGRhdGEgc2V0OiBUb3RhbCB5aWVsZCBpbiBidXNoZWxzIHBlciBhY3JlIGZvciAxMCB2YXJpZXRpZXMKYXQgNiBzaXRlcyBpbiBNaW5uZXNvdGEgaW4gZWFjaCBvZiB0d28geWVhcnMsIDE5MzEgYW5kIDE5MzIuCgpUaGUgcmF3IGRhdGE6CgpgYGB7cn0KZGF0YShiYXJsZXksIHBhY2thZ2UgPSAibGF0dGljZSIpCmhlYWQoYmFybGV5KQpgYGAKClNvbWUgaW5pdGlhbCBwbG90czoKCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KcDEgPC0gZ2dwbG90KGJhcmxleSkgKyBnZW9tX3BvaW50KGFlcyh4ID0geWllbGQsIHkgPSB2YXJpZXR5KSkKcDIgPC0gZ2dwbG90KGJhcmxleSkgKyBnZW9tX3BvaW50KGFlcyh4ID0geWllbGQsIHkgPSBzaXRlKSkKY293cGxvdDo6cGxvdF9ncmlkKHAxLCBwMikKYGBgCgpVc2luZyBjb2xvciB0byBzZXBhcmF0ZSB5aWVsZHMgaW4gdGhlIHR3byB5ZWFyczoKCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KcDEgPC0gZ2dwbG90KGJhcmxleSkgKyBnZW9tX3BvaW50KGFlcyh4ID0geWllbGQsIHkgPSB2YXJpZXR5LCBjb2xvciA9IHllYXIpKQpwMiA8LSBnZ3Bsb3QoYmFybGV5KSArIGdlb21fcG9pbnQoYWVzKHggPSB5aWVsZCwgeSA9IHNpdGUsIGNvbG9yID0geWVhcikpCmNvd3Bsb3Q6OnBsb3RfZ3JpZChwMSwgcDIpCgpgYGAKCkNhbiB3ZSBhbHNvIHNob3cgYHNpdGVgIHVzaW5nIHN5bWJvbCBzaGFwZT8KCmBgYHtyfQpnZ3Bsb3QoYmFybGV5KSArCiAgICBnZW9tX3BvaW50KGFlcyh4ID0geWllbGQsIHkgPSB2YXJpZXR5LCBjb2xvciA9IHllYXIsIHNoYXBlID0gc2l0ZSkpCmBgYAoKVGhlcmUgaXMgYSBsb3Qgb2YgX2ludGVyZmVyZW5jZV8gYmV0d2VlbiBzaGFwZSBhbmQgY29sb3IuCgpQb3NzaWJsZSBpbXByb3ZlbWVudHM6CgoqIGppdHRlcmluZzsKKiBsYXJnZXIgcG9pbnRzLgoKYGBge3J9CmdncGxvdChiYXJsZXkpICsKICAgIGdlb21fcG9pbnQoYWVzKHggPSB5aWVsZCwgeSA9IHZhcmlldHksIGNvbG9yID0geWVhciwgc2hhcGUgPSBzaXRlKSwKICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIoaGVpZ2h0ID0gMC4xNSwgd2lkdGggPSAwKSwKICAgICAgICAgICAgICAgc2l6ZSA9IDIpCmBgYAoKQW5vdGhlciBhcHByb2FjaDogX2ZhY2V0aW5nXyB0byBwcm9kdWNlIF9zbWFsbCBtdWx0aXBsZXNfLgoKYGBge3IsIGZpZy53aWR0aCA9IDEwfQpnZ3Bsb3QoYmFybGV5KSArCiAgICBnZW9tX3BvaW50KGFlcyh4ID0geWllbGQsIHkgPSB2YXJpZXR5LCBjb2xvciA9IHllYXIpKSArCiAgICBmYWNldF93cmFwKH5zaXRlKQpgYGAKCkZvY3VzaW5nIG9uIHN1bW1hcmllcyBjYW4gaGVscC4gX0JhciBjaGFydHNfIGFyZSBzb21ldGltZXMgdXNlZCBmb3IKc3VtbWFyaWVzLCBidXQgX2RvdCBwbG90c18gYXJlIHVzdWFsbHkgYSBiZXR0ZXIgY2hvaWNlLgoKYGBge3IsIGZpZy53aWR0aCA9IDEwfQpiYXJsZXlfc2l0ZV95ZWFyIDwtIHN1bW1hcml6ZShncm91cF9ieShiYXJsZXksIHNpdGUsIHllYXIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5aWVsZCA9IG1lYW4oeWllbGQpKQpwMSA8LSBnZ3Bsb3QoYmFybGV5X3NpdGVfeWVhcikgKwogICAgZ2VvbV9wb2ludChhZXMoeSA9IHNpdGUsIHggPSB5aWVsZCwgY29sb3IgPSB5ZWFyKSwgc2l6ZSA9IDMpCnAyIDwtIGdncGxvdChiYXJsZXlfc2l0ZV95ZWFyKSArCiAgICBnZW9tX2NvbChhZXMoeCA9IHNpdGUsIHkgPSB5aWVsZCwgZmlsbCA9IHllYXIpLAogICAgICAgICAgICAgc2l6ZSA9IDMsCiAgICAgICAgICAgICBwb3NpdGlvbiA9ICJkb2RnZSIsIHdpZHRoID0gLjQpICsKICAgIGNvb3JkX2ZsaXAoKQpjb3dwbG90OjpwbG90X2dyaWQocDEsIHAyKQpgYGAKCkJlY2F1c2Ugb2YgdGhlIHdheSB3ZSBwZXJjZWl2ZSBiYXJzLCBpdCBpcyBpbXBvcnRhbnQgdG8gdXNlIGEgW3plcm8KYmFzZSBsaW5lIGZvciBiYXIKY2hhcnRzXShodHRwczovL2Zsb3dpbmdkYXRhLmNvbS8yMDE1LzA4LzMxL2Jhci1jaGFydC1iYXNlbGluZXMtc3RhcnQtYXQtemVyby8pLgoKIVtdKGltZy92aXozLTUyMHgyOTQuanBnKQoKIVtdKGltZy92aXo1LTUyMHgyODAuanBnKQoKCiMjIyBIYWlyIGFuZCBFeWUgQ29sb3IgRGF0YQoKQSBkYXRhIHNldCByZWNvcmRpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiBoYWlyIGFuZCBleWUgY29sb3IgYW5kIHNleCBpbgo1OTIgc3RhdGlzdGljcyBzdHVkZW50cy4KClRoZSBkYXRhIHNldCBpcyBhdmFpbGFibGUgYXMgYSBfY3Jvc3MtdGFidWxhdGlvbl87IGBhcy5kYXRhLmZyYW1lYApjb252ZXJ0cyBpdCB0byBhIGRhdGEgZnJhbWUuCgpgYGB7cn0KSGFpckV5ZURGIDwtIGFzLmRhdGEuZnJhbWUoSGFpckV5ZUNvbG9yKQpoZWFkKEhhaXJFeWVERikKYGBgCgpMb29raW5nIGF0IHRoZSBkaXN0cmlidXRpb24gb2YgZXllIGNvbG9yOgoJCmBgYHtyfQpleWUgPC0gc3VtbWFyaXplKGdyb3VwX2J5KEhhaXJFeWVERiwgRXllKSwgRnJlcSA9IHN1bShGcmVxKSkKZ2dwbG90KGV5ZSkgKyBnZW9tX2NvbChhZXMoeCA9IEV5ZSwgeSA9IEZyZXEpLCBwb3NpdGlvbiA9ICJkb2RnZSIpCmBgYAoKTWFwcGluZyBleWUgY29sb3IgdG8gY29sb3IgaW4gYWRkaXRpb24gdG8gdGhlIGhvcml6b250YWwgYXhpcyBjYW4gaGVscDoKCmBgYHtyfQpnZ3Bsb3QoZXllKSArIGdlb21fY29sKGFlcyh4ID0gRXllLCB5ID0gRnJlcSwgZmlsbCA9IEV5ZSksIHBvc2l0aW9uID0gImRvZGdlIikKYGBgCgpNb3JlIHNlbnNpYmxlIGNvbG9ycyB3b3VsZCBiZSBuaWNlIGJ1dCByZXF1aXJlcyBhIGJpdCBvZiB3b3JrOgoKYGBge3J9CmhhemVsX3JnYiA8LSBjb2wycmdiKCJicm93biIpICogMC43NSArIGNvbDJyZ2IoImdyZWVuIikgKiAwLjI1CmhhemVsIDwtIGRvLmNhbGwocmdiLCBhcy5saXN0KGhhemVsX3JnYiAvIDI1NSkpCgpjb2xzIDwtIGMoQmx1ZSA9IGNvbG9yc3BhY2U6OmxpZ2h0ZW4oY29sb3JzcGFjZTo6ZGVzYXR1cmF0ZSgiYmx1ZSIsIDAuMyksIDAuMyksCiAgICAgICAgICBHcmVlbiA9IGNvbG9yc3BhY2U6OmxpZ2h0ZW4oImZvcmVzdGdyZWVuIiwgMC4xKSwKICAgICAgICAgIEJyb3duID0gY29sb3JzcGFjZTo6bGlnaHRlbigiYnJvd24iLCAwLjAwMDEpLCAjIyAwLjM/CiAgICAgICAgICBIYXplbCA9IGNvbG9yc3BhY2U6OmxpZ2h0ZW4oaGF6ZWwsIDAuMykpCgpwYiA8LSBnZ3Bsb3QoZXllKSArCiAgICBnZW9tX2NvbChhZXMoeCA9IEV5ZSwgeSA9IEZyZXEsIGZpbGwgPSBFeWUpLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbHMpCnBiCmBgYAoKQSBfc3RhY2tlZCBiYXIgY2hhcnRfIGNhbiBhbHNvIGJlIHVzZWZ1bDoKCmBgYHtyfQpwc2IgPC0gZ2dwbG90KGV5ZSkgKwogICAgZ2VvbV9jb2woYWVzKHggPSAiIiwgeSA9IEZyZXEsIGZpbGwgPSBFeWUpLCBjb2xvciA9ICJsaWdodGdyZXkiKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xzKQpwc2IKYGBgCgpBIF9waWUgY2hhcnRfIGNhbiBiZSBzZWVuIGFzIGEgc3RhY2tlZCBiYXIgY2hhcnQgaW4gcG9sYXIgY29vcmRpbmF0ZXM6CgpgYGB7cn0KKHBwIDwtIHBzYiArIGNvb3JkX3BvbGFyKCJ5IikpCmBgYAoKVGhlIGF4aXMgYW5kIGdyaWQgYXJlIG5vdCBoZWxwZnVsOyBhIF90aGVtZV8gYWRqdXN0bWVudCBjYW4gcmVtb3ZlIHRoZW06CgpgYGB7cn0KKHBwIDwtIHBwICsgdGhlbWVfdm9pZCgpKQpgYGAKCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWluZm8iPiBUaGVtZXMgYXJlIGEgd2F5IHRvIGN1c3RvbWl6ZSB0aGUKbm9uLWRhdGEgY29tcG9uZW50cyBvZiBwbG90czogaS5lLiB0aXRsZXMsIGxhYmVscywgZm9udHMsIGJhY2tncm91bmQsCmdyaWRsaW5lcywgYW5kIGxlZ2VuZHMuIFRoZW1lcyBjYW4gYmUgdXNlZCB0byBnaXZlIHBsb3RzIGEgY29uc2lzdGVudApjdXN0b21pemVkIGxvb2suCgpUaGUgYGdndGhlbWVzYCBwYWNrYWdlIHByb3ZpZGVzIGEgbnVtYmVyIG9mIHRoZW1lcyB0byBlbXVsYXRlIHRoZQpzdHlsZSBvZiBkaWZmZXJlbnQgcHVibGljYXRpb25zLCBmb3IgZXhhbXBsZSBgdGhlbWVfd3NqYCBhbmQKYHRoZW1lX2Vjb25vbWlzdGAuICA8L2Rpdj4KCkhvdyB3ZWxsIGRvIGJhciBjaGFydHMgYW5kIHBpZSBjaGFydHMgd29yaz8KCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KY293cGxvdDo6cGxvdF9ncmlkKHBiLCBwcCkKYGBgCgpTb21lIHF1ZXN0aW9uczoKICAgICAKKiBXaGljaCBwbG90IG1ha2VzIGl0IGVhc2llciB0byB0ZWxsIHdoZXRoZXIgdGhlIHByb3BvcnRpb24gb2YKICBicm93bi1leWVkIHN0dWRlbnRzIGlzIGxhcmdlciBvciBzbWFsbGVyIHRoYXQgdGhlIHByb3BvcnRpb24gb2YKICBibHVlLWV5ZWQgc3R1ZGVudHMuCgoqIFdoaWNoIHBsb3QgbWFrZXMgaXQgZWFzaWVyIHRvIHRlbGwgd2hldGhlciB0aGVzZSBwcm9wb3J0aW9ucyBhcmUKICBsYXJnZXIgb3Igc21hbGxlciB0aGFuIDEvMiBvciAxLzQgb3IgMS8zPwoKTG9va2luZyBhdCB0aGUgcHJvcG9ydGlvbnMgd2l0aGluIGhhaXIgY29sb3IgYW5kIHNleDoKICAgICAgCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KZXllX2hhaXJzZXggPC0gbXV0YXRlKGdyb3VwX2J5KEhhaXJFeWVERiwgSGFpciwgU2V4KSwgUHJvcCA9IEZyZXEgLyBzdW0oRnJlcSkpCnAxIDwtIGdncGxvdChleWVfaGFpcnNleCkgKwogICAgZ2VvbV9jb2woYWVzKHggPSBFeWUsIHkgPSBQcm9wLCBmaWxsID0gRXllKSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29scykgKwogICAgZmFjZXRfZ3JpZChIYWlyflNleCkKcDIgPC0gZ2dwbG90KGV5ZV9oYWlyc2V4KSArCiAgICBnZW9tX2NvbChhZXMoeCA9ICIiLCB5ID0gUHJvcCwgZmlsbCA9IEV5ZSkpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbHMpICsKICAgIGNvb3JkX3BvbGFyKCJ5IikrZmFjZXRfZ3JpZChIYWlyflNleCkgKwogICAgdGhlbWVfdm9pZCgpCmNvd3Bsb3Q6OnBsb3RfZ3JpZChwMSwgcDIpCmBgYAoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+IEEgbW9yZSBjb21wbGV0ZSBgZ2dwbG90YCB0ZW1wbGF0ZToKCmBgYHIKZ2dwbG90KGRhdGEgPSA8REFUQT4pICsKICAgIDxHRU9NPihtYXBwaW5nID0gYWVzKDxNQVBQSU5HUz4pLAogICAgICAgICAgIHN0YXQgPSA8U1RBVD4sCiAgICAgICAgICAgcG9zaXRpb24gPSA8UE9TSVRJT04+KSArCiAgICA8IC4uLiBNT1JFIEdFT01TIC4uLiA+ICsKICAgIDxDT09SRElOQVRFX0FESlVTVE1FTlQ+ICsKICAgIDxTQ0FMRV9BREpVU1RNRU5UPiArCiAgICA8RkFDRVRJTkc+ICsKICAgIDxUSEVNRV9BREpVU1RNRU5UPgpgYGAKPC9kaXY+CgoKIyMgUGVyY2VwdGlvbiBhbmQgdGhlIEdyYW1tYXIgb2YgR3JhcGhpY3MKCmBgYHtyfQpyaXZlciA8LSBzY2FuKCJkYXRhL3JpdmVyLmRhdCIpCnJkIDwtIGRhdGEuZnJhbWUoZmxvdyA9IHJpdmVyLCBtb250aCA9IHNlcV9hbG9uZyhyaXZlcikpCihwcCA8LSBnZ3Bsb3QocmQpICsgZ2VvbV9wb2ludChhZXMoeCA9IG1vbnRoLCB5ID0gZmxvdykpKQpgYGAKCmBgYHtyLCBldmFsID0gRkFMU0V9CihwbCA8LSBnZ3Bsb3QocmQpICsgZ2VvbV9saW5lKGFlcyh4ID0gbW9udGgsIHkgPSBmbG93KSkpCmBgYAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcHAgKyBjb29yZF9maXhlZCgzLjUpCmBgYAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcGwgKyBjb29yZF9maXhlZCgzLjUpCmBgYAoKIyMjIEEgU2ltcGxlIE1vZGVsIG9mIFZpc3VhbCBQZXJjZXB0aW9uCgpUaGUgZXllcyBhY3F1aXJlIGFuIGltYWdlLCB3aGljaCBpcyBwcm9jZXNzZWQgdGhyb3VnaCB0aHJlZSBzdGFnZXMgb2YKbWVtb3J5OgoKKiBJY29uaWMgbWVtb3J5CiogV29ya2luZyBtZW1vcnksIG9yIHNob3J0LXRlcm0gbWVtb3J5CiogTG9uZy10ZXJtIG1lbW9yeQoKVGhlIGZpcnN0IHByb2Nlc3Npbmcgc3RhZ2Ugb2YgYW4gaW1hZ2UgaGFwcGVucyBpbiBpY29uaWMgbWVtb3J5LgoKKiBJbWFnZXMgcmVtYWluIGluIGljb25pYyBtZW1vcnkgZm9yIGxlc3MgdGhhbiBhIHNlY29uZC4KKiBQcm9jZXNzaW5nIGluIGljb25pYyBtZW1vcnkgaXMgbWFzc2l2ZWx5IHBhcmFsbGVsIGFuZCBhdXRvbWF0aWMuCiogVGhpcyBpcyBjYWxsZWQgX3ByZWF0dGVudGl2ZSBwcm9jZXNzaW5nXy4KClByZWF0dGVudGl2ZSBwcm9jZXNzaW5nIGlzIGEgZmFzdCByZWNvZ25pdGlvbiBwcm9jZXNzLgoKTWVhbmluZ2Z1bCB2aXN1YWwgY2h1bmtzIGFyZSBtb3ZlZCBmcm9tIGljb25pYyBtZW1vcnkgdG8gc2hvcnQgdGVybSBtZW1vcnkuCgoqIFRoZXNlIGNodW5rcyBhcmUgdXNlZCBieSBjb25zY2lvdXMsIG9yIGF0dGVudGl2ZSwgcHJvY2Vzc2luZy4KKiBBdHRlbnRpdmUgcHJvY2Vzc2luZyBvZnRlbiBpbnZvbHZlcyBjb25zY2lvdXMgY29tcGFyaXNvbnMgb3Igc2VhcmNoLgoqIFNob3J0IHRlcm0gbWVtb3J5IGlzIGxpbWl0ZWQ7CiAgICAqIGluZm9ybWF0aW9uIGlzIHJldGFpbmVkIGZvciBvbmx5IGEgZmV3IHNlY29uZHM7CiAgICAqIG9ubHkgdGhyZWUgb3IgZm91cnMgY2h1bmtzIGNhbiBiZSBoZWxkIGF0IGEgdGltZS4KCkxvbmcgdGVybSB2aXN1YWwgbWVtb3J5IGlzIGJ1aWx0IHVwIG92ZXIgYSBsaWZldGltZSwgdGhvdWdoCmluZnJlcXVlbnRseSB1c2VkIHZpc3VhbCBjaHVua3MgbWF5IGJlY29tZSBsb3N0LgoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+KipWaXN1YWwgRGVzaWduIEltcGxpY2F0aW9ucyoqCgoqIFRyeSB0byBtYWtlIGFzIG11Y2ggdXNlIG9mIHByZWF0dGVudGl2ZSBmZWF0dXJlcyBhcyBwb3NzaWJsZS4KCiogUmVjb2duaXplIHdoZW4gcHJlYXR0ZW50aXZlIGZlYXR1cmVzIG1pZ2h0IG1pc2xlYWQuCgoqIEZvciBmZWF0dXJlcyB0aGF0IHJlcXVpcmUgYXR0ZW50aXZlIHByb2Nlc3Npbmcga2VlcCBpbiBtaW5kIHRoYXQKICB3b3JraW5nIG1lbW9yeSBpcyBsaW1pdGVkLgo8L2Rpdj4KCiMjIyBTb21lIFRlcm1zIGZvciBEZXNjcmliaW5nIFZpc3VhbGl6YXRpb25zCgotIERhdGEgdG8gYmUgdmlzdWFsaXplZCBjb250YWlucyBfdmFyaWFibGVzXyBvciBfYXR0cmlidXRlc18gbWVhc3VyZWQKICBvbiBpbmRpdmlkdWFsIF9pdGVtc18gb3IgX2Nhc2VzXy4KCiogX0xpbmtzXyBhcmUgcmVsYXRpb25zaGlwcyB0aGF0IG1heSBleGlzdCBhbW9uZyBpdGVtcywgZS5nLiBtb250aHMKICB3aXRoaW4gYSB5ZWFyIG9yIGNvdW50cmllcyB3aXRoaW4gYSBjb250aW5lbnQuCgoqIF9NYXJrc18gYXJlIGluZGl2aWR1YWwgZ2VvbWV0cmljIGVudGl0aWVzIHVzZWQgdG8gcmVwcmVzZW50IGl0ZW1zOgogIHBvaW50cy4gYmFycywgZXRjLgoKKiBfQWVzdGhldGljc18gb3IgX3Zpc3VhbCBjaGFubmVsc18gYXJlIHRoZSB2aXN1YWwgZmVhdHVyZXMgb2YgbWFya3MKICB0aGF0IGNhbiBiZSB1c2VkIHRvIGVuY29kZSBhdHRyaWJ1dGVzLgoKVGhlIGBhZXMoLi4uKWAgZXhwcmVzc2lvbnMgZXN0YWJsaXNoIHRoZSBtYXBwaW5nIGJldHdlZW4gYXR0cmlidXRlcwphbmQgdmlzdWFsIGNoYW5uZWxzLgoKVGhlc2UgaWRlYXMgY2xvc2VseSBtaXJyb3IgdGhlIHN0cnVjdHVyZSBvZiB0aGUgX2dyYW1tYXIgb2YgZ3JhcGhpY3NfCmFzIGltcGxlbWVudGVkIGluIGBnZ3Bsb3RgLgoKCj4gTXVuem5lciwgVC4gKDIwMTQpLCBbX1Zpc3VhbGl6YXRpb24gQW5hbHlzaXMgYW5kCj4gIERlc2lnbl9dKGh0dHA6Ly93d3cuY3MudWJjLmNhL350bW0vdmFkYm9vay8pLCBDUkMgUHJlc3MuCgo+IFdpbGtpbnNvbiwgTC4gKDIwMDUpLCBfVGhlIEdyYW1tYXIgb2YgR3JhcGhpY3NfLCAybmQgZWQsIFNwcmluZ2VyLgoKCiMjIyBDaGFubmVscyBhbmQgdGhlaXIgQWNjdXJhY3kKQSB1c2VmdWwgZGlzdGluY3Rpb24gYW1vbmcgY2hhbm5lbHM6CgoqIF9NYWduaXR1ZGUgY2hhbm5lbHNfIGNhbiByZWZsZWN0IG9yZGVyIGFuZCBudW1lcmljIHZhbHVlcywKICBlLmcuIHBvc2l0aW9uIG9uIGFuIGF4aXMsIGxlbmd0aCwgYXJlYSwgYnJpZ2h0bmVzcy4KCiogX0lkZW50aXR5IGNoYW5uZWxzXyBjYW4gZGlzdGluZ3Vpc2ggZGlmZmVyZW50IHZhbHVlcyBidXQgbm90IHJlZmxlY3QKICBvcmRlciwgZS5nLiBodWUsIHNoYXBlLCBncm91cGluZy4KClNvbWUgY2hhbm5lbHMgYXJlIGJldHRlciBhdCBjb252ZXlpbmcgaW5mb3JtYXRpb24gdGhhbiBvdGhlcnMuCgpNdW56bmVyJ3Mgb3JkZXJpbmcgYnkgYWNjdXJhY3k6CgpNYWduaXR1ZGUgQ2hhbm5lbHMgKE9yZGVyZWQsIE51bWVyaWNhbCkgICAgSWRlbnRpdHkgQ2hhbm5lbHMgKENhdGVnb3JpY2FsKQotLS0tLS0tLS0tICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLS0tLS0KUG9zaXRpb24gb24gY29tbW9uIHNjYWxlICAgICAgICAgICAgICAgICAgIFNwYXRpYWwgZ3JvdXBpbmcKUG9zaXRpb24gb24gdW5hbGlnbmVkIHNjYWxlICAgICAgICAgICAgICAgIENvbG9yIGh1ZQpMZW5ndGggKDFEIHNpemUpICAgICAgICAgICAgICAgICAgICAgICAgICAgU2hhcGUKVGlsdCwgYW5nbGUKQXJlYSAoMkQgc2l6ZSkKRGVwdGggKDNEIHBvc2l0aW9uKQpDb2xvciBsdW1pbmFuY2UsIHNhdHVyYXRpb24KQ3VydmF0dXJlLCB2b2x1bWUgKDNEIHNpemUpCgpMaW5lIHdpZHRoIGlzIGFub3RoZXIgY2hhbm5lbDsgbm90IHN1cmUgdGhlcmUgaXMgYWdyZWVtZW50IG9uIGl0cwphY2N1cmFjeSwgYnV0IGl0IGlzIG5vdCBoaWdoLgoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+KipWaXN1YWwgRGVzaWduIEltcGxpY2F0aW9ucyoqCgpUcnkgdG8gbWFwIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgdG8gdGhlIHN0cm9uZ2VzdCBjaGFubmVscy4KPC9kaXY+CgoKIyMjIENvbG9yCgpDb2xvciBpcyB2ZXJ5IGVmZmVjdGl2ZSB3aGVuIHVzZWQgd2VsbC4KCkJ1dCB1c2luZyBjb2xvciB3ZWxsIGlzIG5vdCBlYXN5LgoKU29tZSBvZiB0aGUgaXNzdWVzOgoKKiBQZXJjZXB0aW9uIGRlcGVuZHMgb24gY29udGV4dC4KCiogU2ltcGxlIGNvbG9yIGFzc2lnbm1lbnRzIG1heSBub3Qgc2VwYXJhdGUgZXF1YWxseSB3ZWxsLgoKKiBFZmZlY3RpdmVuZXNzIG1heSB2YXJ5IHdpdGggdGhlIG1lZGl1bSAoc2NyZWVuLCBwcm9qZWN0b3IsIHByaW50KS4KCiogU29tZSBwZW9wbGUgZG8gbm90IHBlcmNlaXZlIHRoZSBmdWxsIHNwZWN0dXJtIG9mIGNvbG9ycy4KCiogR3JleSBzY2FsZSBwcmludGluZy4KCiogU29tZSBjb2xvcnMgaGF2ZSBjdWx0dXJhbCBzaWduaWZpY2FuY2UuCgoqIEN1bHR1cmFsIHNpZ25pZmljYW5jZSBtYXkgdmFyeSBhbW9uZyBjdWx0dXJlcyBhbmQgd2l0aCB0aW1lLgoKQ29sb3IgcGVyY2VwdGlvbiBpcyByZWxhdGl2ZToKCiFbXShpbWcvY2hlc3MxLnBuZykgIVtdKGltZy9jaGVzczIucG5nKQoKQSBub3RlIG9uIFtyYWluYm93IGNvbG9yc10oCmh0dHBzOi8vZWVlY29uLnVpYmsuYWMuYXQvfnplaWxlaXMvbmV3cy9lbmRyYWluYm93LykuCgpTb21lIHRvb2xzIGZvciBzZWxlY3RpbmcgcGFsZXR0ZXMgaW5jbHVkZToKCiogW0NvbG9yQnJld2VyXShodHRwOi8vY29sb3JicmV3ZXIyLm9yZyk7IGF2YWlsYWJsZSBpbiB0aGUKICBgUkNvbG9yQnJld2VyYCBwYWNrYWdlLgoKKiBbSENMIFdpemFyZF0oaHR0cDovL3d3dy5oY2x3aXphcmQub3JnLyk7IGFsc28gYXZhaWxhYmxlIGFzIGBoY2x3aXphcmRgCiAgaW4gdGhlIGBjb2xvcnNwYWNlYCBwYWNrYWdlLgoKCiMjIEEgR3JhbW1hciBvZiBEYXRhIE1hbmlwdWxhdGlvbgoKVGhlIGBkcGx5cmAgcGFja2FnZSBwcm92aWRlcyBhIGxhbmd1YWdlLCBvciBncmFtbWFyLCBmb3IgZGF0YQptYW5pcHVsYXRpb24uCgpUaGUgbGFuZ3VhZ2UgY29udGFpbnMgYSBudW1iZXIgb2YgX3ZlcmJzXyB0aGF0IG9wZXJhdGUgb24gdGFibGVzLgoKVGhlIG1vc3QgY29tbW9ubHkgdXNlZCB2ZXJicyBvcGVyYXRlIG9uIGEgc2luZ2xlIGRhdGEgZnJhbWU6CgoqIGBzZWxlY3RgOiBwaWNrIHZhcmlhYmxlcyBieSB0aGVpciBuYW1lcwoqIGBmaWx0ZXJgOiBjaG9vc2Ugcm93cyB0aGF0IHNhdGlzZnkgc29tZSBjcml0ZXJpYQoqIGBtdXRhdGVgOiBjcmVhdGUgdHJhbnNmb3JtZWQgb3IgZGVyaXZlZCB2YXJpYWJsZXMKKiBgYXJyYW5nZWA6IHJlb3JkZXIgdGhlIHJvd3MKKiBgc3VtbWFyaXplYDogY29sbGFwc2Ugcm93cyBkb3duIHRvIHN1bW1hcmllcwoKVGhlcmUgYXJlIGFsc28gYSBudW1iZXIgb2YgYGpvaW5gIHZlcmJzIHRoYXQgbWVyZ2Ugc2V2ZXJhbCBkYXRhIGZyYW1lcwppbnRvIG9uZS4KClRoZSBgdGlkeXJgIHBhY2thZ2UgcHJvdmlkZXMgYWRkaXRpb25hbCB2ZXJicywgc3VjaCBhcyBgcGl2b3RfbG9uZ2VyYAphbmQgYHBpdm90X3dpZGVyYCBmb3IgcmVzaGFwaW5nIGRhdGEgZnJhbWVzLgoKVGhlIHNpbmdsZSB0YWJsZSB2ZXJicyBjYW4gYWxzbyBiZSB1c2VkIHdpdGggYGdyb3VwX2J5YCB0byB3b3JrIGEKZ3JvdXAgYXQgYSB0aW1lIGluc3RlYWQgb2YgYXBwbHlpbmcgdG8gdGhlIGVudGlyZSBkYXRhIGZyYW1lLgoKVGhlIGRlc2lnbiBvZiBgZHBseXJgIGlzIHN0cm9uZ2x5IG1vdGl2YXRlZCBieSBTUUwuCgoKIyMgTW9yZSBFeGFtcGxlcwoKVGhlc2UgZXhhbXBsZXMgc3RhcnQgd2l0aCByYXcgZGF0YSBhcyB5b3UgbWlnaHQgcmVjZWl2ZSBpdCBmcm9tIGEKcmVzZWFyY2hlciwgYW5kIGludm9sdmUgcmVhZGluZyBhbmQgY2xlYW5pbmcgdGhlIGRhdGEuCgoKIyMjIFdpbmQgVHVyYmluZXMgaW4gSW93YQoKVGhlcmUgYXJlIG1hbnkgd2luZCB0dXJiaW5lcyBpbiBJb3dhLiAgRGF0YSBpcyBhdmFpbGFibGUgZnJvbSB0aGUKW1UuUy4gV2luZCBUdXJiaW5lIERhdGFiYXNlXShodHRwczovL2VlcnNjbWFwLnVzZ3MuZ292L3Vzd3RkYi8pLiAgQQpzbmFwc2hvdCBpcyBhdmFpbGFibGUgaXMgW2hlcmVdKGRhdGEvdXNfd2luZC5jc3YpIGFzIGEgQ1NWIGZpbGUuCgpDU1YgZmlsZXMgYXJlIGEgY29tbW9uIGZvcm0gb2YgZGF0YSBleGNoYW5nZS4KCiogVGhleSBhcmUgc2ltcGxlIHRleHQgZmlsZXMgdGhhdCBhcmUgaW50ZW5kZWQgdG8gYmUgd3JpdHRlbiBhbmQgcmVhZAogIGJ5IGEgY29tcHV0ZXIuCgoqIFNvbWUgQ1NWIGZpbGVzIGluY2x1ZGUgYSBoZWFkZXIgYW5kIGEgZm9vdGVyIHRoYXQgbmVlZCB0byBoZSBoYW5kbGVkLgoKKiBPbmUgaXNzdWUgaXMgdGhhdCBhIGNvbW1hIGlzbid0IGEgZ29vZCBzZXBhcmF0b3IgaW4gY291bnRyaWVzIHdoZXJlCiAgaXQgaXMgdGhlIGRlY2ltYWwgc2VwYXJhdG9yIQoKKiBBIENTViBmaWxlIGNhbiBiZSByZWFkIHVzaW5nIGByZWFkLmNzdmAgb3IgYHJlYWRyOjpyZWFkX2NzdmAuCgpSZWFkaW5nIHRoZSB3aW5kIHR1cmJpbmUgZGF0YToKCmBgYHtyfQp3aW5kX3R1cmJpbmVzIDwtIHJlYWQuY3N2KCJkYXRhL3VzX3dpbmQuY3N2IiwgY29tbWVudCA9ICIjIikKYGBgClNvbWUgZGF0YSBjbGVhbmluZyBpcyBuZWVkZWQuCgpGb2N1cyBvbiB0aGUgd2luZCB0dXJiaW5lcyBpbiBJT1dBICgxOSBpcyB0aGUgW0ZJUFMgY291bnR5CmNvZGVdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0ZJUFNfY291bnR5X2NvZGUpIGZvciBJb3dhKToKCmBgYHtyfQp3dF9JQSA8LSBmaWx0ZXIod2luZF90dXJiaW5lcywgdF9maXBzICUvJSAxMDAwID09IDE5KQpgYGAKRHJvcCBlbnRyaWVzIHdpdGggbWlzc2luZyBsb25naXR1ZGUgb3IgbGF0aXR1ZGUgdmFsdWVzOgoKYGBge3J9Cnd0X0lBIDwtIGZpbHRlcih3dF9JQSwgISBpcy5uYSh4bG9uZyksICEgaXMubmEoeWxhdCkpCmBgYApTb21lIG1pc3NpbmcgeWVhciB2YWx1ZXMgYXJlIGVuY29kZWQgYXMgLTk5OTk7IHJlcGxhY2UgdGhlc2Ugd2l0aCBgTkFgOgoKYGBge3J9Cnd0X0lBIDwtIG11dGF0ZSh3dF9JQSwgcF95ZWFyID0gcmVwbGFjZShwX3llYXIsIHBfeWVhciA8IDAsIE5BKSkKYGBgClRvIHNob3cgdGhlIGxvY2F0aW9ucyBvZiB3aW5kIHR1cmJpbmVzIG9uIGEgbWFwLCBsb2FkIHNvbWUgbWFwIGRhdGE6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQppb3dhX3NmIDwtIHNmOjpzdF9hc19zZihtYXBzOjptYXAoImNvdW50eSIsICJpb3dhIiwgcGxvdCA9IEZBTFNFLCBmaWxsID0gVFJVRSkpCmBgYApUbyBzaG93IHRoZSBsb2NhdGlvbnMgb2Ygd2luZCB0dXJiaW5lcyBvbiBhIG1hcCwgbG9hZCBzb21lIG1hcCBkYXRhOgoKYGBge3IsIGZpZy53aWR0aCA9IDh9Cmlvd2Ffc2YgPC0KICAgIHNmOjpzdF9hc19zZihtYXBzOjptYXAoImNvdW50eSIsICJpb3dhIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gVFJVRSkpCnAgPC0gZ2dwbG90KCkgKwogICAgZ2VvbV9zZihkYXRhID0gaW93YV9zZikgKwogICAgZ2d0aGVtZXM6OnRoZW1lX21hcCgpCnAKYGBgCgpMb2NhdGlvbnMgZm9yIGFsbCB3aW5kIHR1cmJpbmVzIGluIGlvd2E6CgpgYGB7ciwgZmlnLndpZHRoID0gOH0KcCArIGdlb21fcG9pbnQoYWVzKHhsb25nLCB5bGF0KSwgZGF0YSA9IHd0X0lBKQpgYGAKClVzaW5nIGNvbG9yIHRvIHNob3cgd2hlbiB0aGUgd2luZCB0dXJiaW5lcyB3ZXJlICBidWlsdDoKCmBgYHtyLCBmaWcud2lkdGggPSA4fQp5ZWFyX2JyayA8LWMoMCwgMjAwNSwgMjAxMCwgMjAxNSwgMjAyMCkKeWVhcl9sYWIgPC0gYygiYmVmb3JlIDIwMDUiLAogICAgICAgICAgICAgICIyMDA1LTIwMDkiLAogICAgICAgICAgICAgICIyMDEwLTIwMTQiLAogICAgICAgICAgICAgICIyMDE1LTIwMjAiKQp3dF9JQSA8LQogICAgbXV0YXRlKHd0X0lBLAogICAgICAgICAgIHllYXIgPSBjdXQocF95ZWFyLAogICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0geWVhcl9icmssCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSB5ZWFyX2xhYiwKICAgICAgICAgICAgICAgICAgICAgIHJpZ2h0ID0gRkFMU0UpKQpwICsgZ2VvbV9wb2ludChhZXMoeGxvbmcsCiAgICAgICAgICAgICAgICAgICB5bGF0LAogICAgICAgICAgICAgICAgICAgY29sb3IgPSB5ZWFyKSwKICAgICAgICAgICAgICAgZGF0YSA9IHd0X0lBLAogICAgICAgICAgICAgICBzaXplID0gMykKYGBgCgpgYGB7ciBldmFsID0gRkFMU0UsIGVjaG8gPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCnAgPC0gZ2dwbG90KCkgKyBnZW9tX3NmKGRhdGEgPSBpb3dhX3NmKSArIGdndGhlbWVzOjp0aGVtZV9tYXAoKQpwICsgZ2VvbV9wb2ludChhZXMoeGxvbmcsIHlsYXQpLCBkYXRhID0gd3RfSUEpCgp3dF9JQV9zZiA8LSBzZjo6c3RfYXNfc2Yod3RfSUEsIGNvb3JkcyA9IGMoInhsb25nIiwgInlsYXQiKSwgY3JzID0gNDMyNikKCnAgKyBnZW9tX3NmKGRhdGEgPSBmaWx0ZXIod3RfSUFfc2YsIHllYXIgPD0gMjAyMCkpCgpsaWJyYXJ5KGdnYW5pbWF0ZSkKcGEgPC0gcCArIGdlb21fc2YoZGF0YSA9IHd0X0lBX3NmKSArCiAgICB0cmFuc2l0aW9uX21hbnVhbCh5ZWFyLCBjdW11bGF0aXZlID0gVFJVRSkgKwogICAgbGFicyh0aXRsZSA9ICJXaW5kIHR1cmJpbmVzIGluIElvd2EiLAogICAgICAgICBzdWJ0aXRsZSA9ICJZZWFyID0ge2N1cnJlbnRfZnJhbWV9IikKYW5pbV9zYXZlKCJmb28uZ2lmIiwgYW5pbWF0ZShwYSwgZnBzID0gMTAsIG5mcmFtZXMgPSAxMDApKQpgYGAKCiMjIyBDYW5jZXIgTWFwCgpUaGUgd2Vic2l0ZSA8aHR0cDovL3d3dy5jYW5jZXItcmF0ZXMuaW5mby9pYT4gcHJvdmlkZXMgZGF0YSBvbgpjYW5jZXIgaW5jaWRlbmNlIGZvciBhIG51bWJlciBvZiBkaWZmZXJlbnQgY2FuY2VycyBpbiBJb3dhLgpUaGUgZGF0YSBmb3IgbHVuZyBhbmQgYnJvbmNodXMgY2FuY2VyIGluIDIwMTEgYXJlIGF2YWlsYWJsZQppbiBhIFtjc3YgZmlsZV0oZGF0YS9JbnZhc2l2ZS1DYW5jZXItSW5jaWRlbmNlLVJhdGVzLWJ5LUNvdW50eS1pbi1Jb3dhLUx1bmctYW5kLUJyb25jaHVzLTIwMTEuY3N2KSBpbiB0aGUgcHJvamVjdC4KCldlIGNhbiByZWFkIHRoZSBmaWxlIHdpdGggYHJlYWRfY3N2YCBmcm9tIHRoZSBgcmVhZHJgIHBhY2thZ2UuCgpMb29raW5nIGF0IHRoZSBmaWxlIHNob3dzIHNvbWUgdGhpbmdzIHRoYXQgbmVlZCB0byBiZSBjbGVhbmVkIHVwOgoKLSBUd28gaGVhZGVyIGxpbmVzIGF0IHRoZSBiZWdpbm5pbmcKLSBTb21lIGZvb3RlciBsaW5lcy4KLSBTb21lIHZhbHVlcyBjb2RlcyBhcyBgfmAuCgpUaGUgaGVhZGVyIGNhbiBiZSBoYW5kbGVkIGJ5IHVzaW5nIGBza2lwID0gMmAgaW4gdGhlIGByZWFkX2NzdmAgY2FsbDoKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CmZuYW1lIDwtICJkYXRhL0ludmFzaXZlLUNhbmNlci1JbmNpZGVuY2UtUmF0ZXMtYnktQ291bnR5LWluLUlvd2EtTHVuZy1hbmQtQnJvbmNodXMtMjAxMS5jc3YiCmQgPC0gcmVhZF9jc3YoZm5hbWUsIHNraXAgPSAyKQpoZWFkKGQpCmBgYAoKTGV0J3MgZm9jdXMgb24gYSBmZXcgdmFyaWFibGVzIGFuZCBnaXZlIHRoZW0gbW9yZSBjb252ZW5pZW50IG5hbWVzOgoKYGBge3J9CmQgPC0gc2VsZWN0KGQsIGNvdW50eSA9IDEsIHBvcHVsYXRpb24gPSAyLCBjb3VudCA9IDMsIGNydWRlX3JhdGUgPSA0KQpgYGAKClRoZSBmb290ZXIgbmVlZHMgdG8gYmUgcmVtb3ZlZDoKCmBgYHtyfQp0YWlsKGQpCmBgYAoKT25lIHdheSB0byByZW1vdmUgdGhlIGZvb3RlcjoKCmBgYHtyfQpkIDwtIGZpbHRlcihkLCAhIGlzLm5hKHBvcHVsYXRpb24pKQpkIDwtIGZpbHRlcihkLCBjb3VudHkgIT0gIlNUQVRFIikKdGFpbChkKQpgYGAKCkNoYW5naW5nIGBjb3VudGAgYW5kIGBjcnVkZV9yYXRlYCB0byBudW1lcmljIGNoYW5nZXMgdGhlIGB+YCBlbnRyaWVzCnRvIG1pc3NpbmcgdmFsdWVzIChgTkFgKSB2YWx1ZXM6CgpgYGB7cn0KZCA8LSBtdXRhdGUoZCwgY291bnQgPSBhcy5udW1lcmljKGNvdW50KSwgY3J1ZGVfcmF0ZSA9IGFzLm51bWVyaWMoY3J1ZGVfcmF0ZSkpCmBgYAoKSW4gdGhpcyBjYXNlIHRoZXJlIGFyZSBubyB6ZXJvIGNhc2UgdmFsdWVzOyB0d28gd2F5cyB0byBjaGVjazoKCmBgYHtyfQpjb3VudChkLCBjb3VudCA9PSAwKQphbnkoZCRjb3VudCA9PSAwLCBuYS5ybSA9IFRSVUUpCmBgYAoKSXQgX21pZ2h0XyBiZSByZWFzb25hYmxlIHRvIGFzc3VtZSB0aGVzZSB2YWx1ZXMgd2hlcmUgemVybywgc28gcmVwbGFjZQp0aGVtIHdpdGggemVyb3M6CgpgYGB7cn0KZCA8LSByZXBsYWNlX25hKGQsIGxpc3QoY291bnQgPSAwLCBjcnVkZV9yYXRlID0gMCkpCmBgYAoKQSBfY2hvcm9wbGV0aCBtYXBfIHVzZXMgY29sb3Igb3Igc2hhZGluZyB0byByZXByZXNlbnQgdmFsdWVzIG1lYXN1cmVkCmZvciBkaWZmZXJlbnQgZ2VvZ3JhcGhpYyByZWdpb25zLgoKV2Ugd2lsbCBuZWVkIHRvIG1lcmdlLCBvciBfbGVmdCBqb2luXywgdGhlIGNhbmNlciBkYXRhIHdpdGggdGhlIG1hcApkYXRlIHdlIGxvYWRlZCBmb3IgdGhlIHdpbmQgdHVyYmluZSBtYXAuCgpGb3IgSW93YSB0aGlzIGNhbiBiZSBkb25lIHdpdGggdGhlIGNvdW50eSBuYW1lLCBidXQgc29tZSBjYXJlIGlzIG5lZWRlZC4KCmBgYHtyfQpkJGNvdW50eVsxXQppb3dhX3NmJElEWzFdCgpkIDwtIG11dGF0ZShkLCBjbmFtZSA9IGNvdW50eSwgY291bnR5ID0gdG9sb3dlcihjb3VudHkpKQppb3dhX3NmIDwtIG11dGF0ZShpb3dhX3NmLCBjb3VudHkgPSBzdWIoImlvd2EsIiwgIiIsIElEKSkKCnNldGRpZmYoZCRjb3VudHksIGlvd2Ffc2YkY291bnR5KQpzZXRkaWZmKGlvd2Ffc2YkY291bnR5LCBkJGNvdW50eSkKCmQgPC0gbXV0YXRlKGQsIGNvdW50eSA9IHN1YigiJyIsICIiLCBjb3VudHkpKQoKc2V0ZGlmZihkJGNvdW50eSwgaW93YV9zZiRjb3VudHkpCnNldGRpZmYoaW93YV9zZiRjb3VudHksIGQkY291bnR5KQpgYGAKCkRlZmluZSBgcmF0ZTFLYCB2YXJpYWJsZSBhcyB0aGUgbnVtYmVyIG9mIGNhc2VzIHBlciAxMDAwIGluaGFiaXRhbnRzCmFuZCBsZWZ0IGpvaW4gdGhlIGRhdGEgdG8gdGhlIHBvbHlnb25zOgoKYGBge3J9CmQgPC0gbXV0YXRlKGQsIHJhdGUxSyA9IDEwMDAgKiAoY291bnQgLyBwb3B1bGF0aW9uKSkKbWQgPC0gbGVmdF9qb2luKGlvd2Ffc2YsIGQsICJjb3VudHkiKQpoZWFkKG1kKQpgYGAKCkEgc2ltcGxlIG1hcDoKCmBgYHtyfQpsaWJyYXJ5KGdndGhlbWVzKQpsaWJyYXJ5KHZpcmlkaXMpCmdncGxvdChtZCkgKyBnZW9tX3NmKGFlcyhmaWxsID0gcmF0ZTFLKSkKYGBgCgpBbiBpbXByb3ZlZCB2ZXJzaW9uOgoKYGBge3J9CmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkodmlyaWRpcykKZ2dwbG90KG1kKSArCiAgICBnZW9tX3NmKGFlcyhmaWxsID0gcmF0ZTFLKSwKICAgICAgICAgICAgY29sb3IgPSAiZ3JleSIpICsKICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhuYW1lID0gIlJhdGUgcGVyIDEwMDAiKSArCiAgICB0aGVtZV9tYXAoKQpgYGAKCkEgc2ltcGxlIGludGVyYWN0aXZlIHZlcnNpb24gdXNpbmcgW2BwbG90bHlgXShodHRwczovL3Bsb3QubHkvci8pOgoKYGBge3J9Cm1kbCA8LSBtdXRhdGUobWQsCiAgICAgICAgICAgICAgbGFiZWwgPSBwYXN0ZShjbmFtZSwgcm91bmQocmF0ZTFLLCAxKSwgcG9wdWxhdGlvbiwgc2VwID0gIlxuIikpCnAgPC0gZ2dwbG90KG1kbCkgKwogICAgZ2VvbV9zZihhZXMoZmlsbCA9IHJhdGUxSywKICAgICAgICAgICAgICAgIHRleHQgPSBsYWJlbCksIAogICAgICAgICAgICBjb2xvciA9ICJncmV5IikgKwogICAgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWUgPSAiUmF0ZSBwZXIgMTAwMCIpICsKICAgIHRoZW1lX21hcCgpCgpwbG90bHk6OmdncGxvdGx5KHAsIHRvb2x0aXAgPSAidGV4dCIpCmBgYApUaGUgW2BsZWFmbGV0YF0oaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0LykgcGFja2FnZSBzdXBwb3J0cwptb3JlIHNvcGhpc3RpY2F0ZWQgaW50ZXJhY3RpdmUgbWFwczoKCjwhLS0gaHR0cDovL3JzdHVkaW8uZ2l0aHViLmlvL2xlYWZsZXQvbGVnZW5kcy5odG1sLS0+CmBgYHtyfQpsaWJyYXJ5KGxlYWZsZXQpCnBhbCA8LSBjb2xvck51bWVyaWMoCiAgICBwYWxldHRlID0gInZpcmlkaXMiLAogICAgZG9tYWluID0gbWQkcmF0ZTFLKQpsYWIgPC0gbGFwcGx5KHBhc3RlMCh0b29sczo6dG9UaXRsZUNhc2UobWQkY291bnR5KSwgIjxCUj4iLAogICAgICAgICAgICAgICAgICAgICAiUmF0ZTogIiwgcm91bmQobWQkcmF0ZTFLLCAxKSwgIjxCUj4iLAogICAgICAgICAgICAgICAgICAgICAiUG9wOiAiLCBzY2FsZXM6OmNvbW1hKG1kJHBvcHVsYXRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWNjdXJhY3kgPSAxKSksCiAgICAgICAgICAgICAgaHRtbHRvb2xzOjpIVE1MKQpsZWFmbGV0KHNmOjpzdF90cmFuc2Zvcm0obWQsIDQzMjYpKSAlPiUKICAgIGFkZFBvbHlnb25zKHdlaWdodCA9IDIsCiAgICAgICAgICAgICAgICBjb2xvciA9ICJncmV5IiwKICAgICAgICAgICAgICAgIGZpbGxDb2xvciA9IH4gcGFsKHJhdGUxSyksCiAgICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDEsCiAgICAgICAgICAgICAgICBoaWdobGlnaHRPcHRpb25zID0KICAgICAgICAgICAgICAgICAgICBoaWdobGlnaHRPcHRpb25zKGNvbG9yID0gIndoaXRlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmluZ1RvRnJvbnQgPSBUUlVFKSwKICAgICAgICAgICAgICAgIGxhYmVsID0gbGFiKSAlPiUKICAgIGFkZExlZ2VuZChwYWwgPSBwYWwsIHZhbHVlcyA9IH4gcmF0ZTFLKQpgYGAKCgojIyMgVW5lbXBsb3ltZW50IE1hcAoKW0xvY2FsIEFyZWEgVW5lbXBsb3ltZW50IFN0YXRpc3RpY3MgcGFnZV0oaHR0cHM6Ly93d3cuYmxzLmdvdi9sYXUvKQpmcm9tIHRoZSBCdXJlYXUgb2YgTGFib3IgU3RhdGlzdGljcyBtYWtlcyBhdmFpbGFibGUgY291bnR5LWxldmVsCm1vbnRobHkgdW5lbXBsb3ltZW50IGRhdGEgZm9yIGEgMTQtbW9udGggd2luZG93LiBUaGUgZmlsZSBmb3IgTm92ZW1iZXIKMjAxNiB0aHJvdWdoIERlY2VtYmVyIDIwMTcgaXMgYXZhaWxhYmxlIGlzIGF2YWlsYWJsZSBhdAo8aHR0cDovL3d3dy5zdGF0LnVpb3dhLmVkdS9+bHVrZS9kYXRhL2xhdXMvbGF1Y250eWN1cjE0LTIwMTcudHh0PgphbmQgaW4gdGhlIHByb2plY3QgZGF0YSBmb2xkZXIuCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4gVGhpcyBmaWxlIGlzIGEgdGV4dCBmaWxlIGJ1dCB1c2VzIGEKbm9uLXN0YW5kYXJkIHNlcGFyYXRvci4gSXQgaXMgZGVzaWduZWQgZm9yIGh1bWFuIHJlYWRhYmlsaXR5IGFuZCB1c2VzCmEgY29tbWEgYXMgYSBfdGhvdXNhbmRzIHNlcGFyYXRvcl8gb3IgX2dyb3VwaW5nIG1hcmtfLiAgSXQgYWxzbwppbmNsdWRlcyBoZWFkZXIgYW5kIGZvb3RlciBpbmZvcm1hdGlvbi4gSXQgaXMgc3RpbGwgcmVhc29uYWJseSBlYXN5IHRvCnJlYWQgaW4uICA8L2Rpdj4KCk9uZSB3YXkgdG8gcmVhZCB0aGUgZGF0YSBpbnRvIFIgaXM6CgpgYGB7cn0KbGF1c1VSTCA8LSAiZGF0YS9sYXVjbnR5Y3VyMTQtMjAxNy50eHQiCmxhdXNVUyA8LSByZWFkLnRhYmxlKGxhdXNVUkwsCiAgICAgICAgICAgICAgICAgICAgIGNvbC5uYW1lcyA9IGMoIkxBVVNBcmVhQ29kZSIsICJTdGF0ZSIsICJDb3VudHkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJUaXRsZSIsICJQZXJpb2QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJMYWJvckZvcmNlIiwgIkVtcGxveWVkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVW5lbXBsb3llZCIsICJVbmVtcFJhdGUiKSwKICAgICAgICAgICAgICAgICAgICAgcXVvdGUgPSAnIicsIHNlcCA9ICJ8Iiwgc2tpcCA9IDYsCiAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSwgc3RyaXAud2hpdGUgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICBmaWxsID0gVFJVRSkKZm9vdHN0YXJ0IDwtIGdyZXAoIi0tLS0tLSIsIGxhdXNVUyRMQVVTQXJlYUNvZGUpCmxhdXNVUyA8LSBsYXVzVVNbMTooZm9vdHN0YXJ0IC0gMSksXQpgYGAKCkl0IG1heSBiZSB1c2VmdWwgdG8gYmUgYWJsZSB0byBhY2Nlc3MgdGhlIGNvdW50eSBuYW1lIGFuZCBzdGF0ZSBuYW1lCnNlcGFyYXRlbHk6CgpgYGB7cn0KbGF1c1VTIDwtIHNlcGFyYXRlKGxhdXNVUywgVGl0bGUsIGMoImNuYW1lIiwgInNjb2RlIiksCiAgICAgICAgICAgICAgICAgICBzZXAgPSAiLCAiLCBmaWxsID0gInJpZ2h0IikKYGBgCgpUaGUgYFVuZW1wUmF0ZWAgdmFyaWFibGUgaXMgcmVhZCBhcyBjaGFyYWN0ZXIgZGF0YSBiZWNhdXNlIG9mIG1pc3NpbmcKdmFsdWUgZW5jb2RpbmcsIHNvIG5lZWRzIHRvIGJlIGNvbnZlcnRlZCB0byBudW1lcmljOgoKYGBge3J9CmxhdXNVUyA8LSBtdXRhdGUobGF1c1VTLCBVbmVtcFJhdGUgPSBhcy5udW1lcmljKFVuZW1wUmF0ZSkpCmBgYAoKQ2hlY2sgZm9yIG1pc3NpbmcgdmFsdWVzOgoKYGBge3J9CnNhcHBseShsYXVzVVMsIGZ1bmN0aW9uKHgpIGFueShpcy5uYSh4KSkpCmBgYAoKVGhlIHN0YXRlIGNvZGUgaXMgbWlzc2luZyBmb3IgdGhlIERpc3RyaWN0IG9mIENvbHVtYmlhOgoKYGBge3J9CnVuaXF1ZShmaWx0ZXIoc2VsZWN0KGxhdXNVUywgY25hbWUsIHNjb2RlKSwgaXMubmEoc2NvZGUpKSkKYGBgCgpNaXNzaW5nIHZhbHVlcyBmb3IgYFVuZW1wUmF0ZWAgYXJlIGFsbCBmb3IgUHVlcnRvIFJpY28gYW5kIFNlcHRlbWJlcgoyMDE3LiBIdXJyaWNhbmUgTWFyaWEgbWFkZSBsYW5kZmFsbCBvbiBTZXB0ZW1iZXIgMjAuCgpgYGB7cn0KdW5pcXVlKGZpbHRlcihzZWxlY3QobGF1c1VTLCBzY29kZSwgUGVyaW9kLCBVbmVtcFJhdGUpLCBpcy5uYShVbmVtcFJhdGUpKSkKYGBgCgpBdmVyYWdlIHVuZW1wbG95bWVudCByYXRlcyBvdmVyIHRoZSBwZXJpb2QgY2FuIGJlIGNvbXB1dGVkIGFzCgpgYGB7cn0KYXZnVVMgPC0gc3VtbWFyaXplKGdyb3VwX2J5KGxhdXNVUywgQ291bnR5LCBTdGF0ZSksCiAgICAgICAgICAgICAgICAgICBhdmdfdW5lbXAgPSBtZWFuKFVuZW1wUmF0ZSksCiAgICAgICAgICAgICAgICAgICBjbmFtZSA9IHVuaXF1ZShjbmFtZSksCiAgICAgICAgICAgICAgICAgICBzY29kZSA9IHVuaXF1ZShzY29kZSkpCmhlYWQoYXZnVVMpCmBgYAoKVG8gc2hvdyBhdmVyYWdlIHVuZW1wbG95bWVudCByYXRlcyBvbiBhIG1hcCB3ZSBuZWVkIHRvIG1lcmdlIHRoZQp1bmVtcGxveW1lbnQgZGF0YSB3aXRoIG1hcCBkYXRhLgoKSXQgaXMgc2FmZXIgdG8gdXNlIHRoZSBudW1lcmljIFtGSVBTIGNvdW50eQpjb2RlXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9GSVBTX2NvdW50eV9jb2RlKSwgd2hpY2ggY2FuIGJlCmNvbXB1dGVkIGFzCgpBZGQgRklQUyBjb2RlIHRvIGBhdmdVU2A6CgpgYGB7cn0KYXZnVVMgPC0gbXV0YXRlKGF2Z1VTLCBmaXBzID0gMTAwMCAqIFN0YXRlICsgQ291bnR5KQpoZWFkKGF2Z1VTKQpgYGAKClRoZSBgY291bnR5LmZpcHNgIGRhdGEgZnJhbWUgaW4gdGhlIGBtYXBzYCBwYWNrYWdlIGxpbmtzIHRoZSBGSVBTIGNvZGUKdG8gcmVnaW9uIG5hbWVzIHVzZWQgYnkgdGhlIG1hcCBkYXRhIGluIHRoZSBgbWFwc2AgcGFja2FnZS4KCmBgYHtyfQpsaWJyYXJ5KG1hcHMpCmhlYWQoY291bnR5LmZpcHMpCmBgYAoKU29tZSBpc3N1ZXM6CgpgYGB7cn0KZmlsdGVyKGNvdW50eS5maXBzLCBncmVwbCgiZmxvcmlkYSxvIiwgcG9seW5hbWUpKQpoZWFkKHNlbGVjdChmaWx0ZXIobGF1c1VTLCBzY29kZSA9PSAiTEEiKSwgY25hbWUpKQpgYGAKCkNsZWFuaW5nIHVwIGEgYml0OgoKYGBge3J9CmNvdW50eS5maXBzIDwtIHNlcGFyYXRlKGNvdW50eS5maXBzLCBwb2x5bmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgYygic3RhdGUiLCAiY291bnR5IiwgInBhcnQiKSwKICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIlssOl0iLCBmaWxsID0gInJpZ2h0IikKaGVhZChjb3VudHkuZmlwcykKYGBgCgpDb3VudHkgbWFwIGRhdGEgZm9yIHRoZSBsb3dlciA0OCBzdGF0ZXM6CgpgYGB7cn0KY291bnRpZXNfVVMgPC0gbWFwX2RhdGEoImNvdW50eSIpCmNvdW50aWVzX1VTIDwtIHJlbmFtZShjb3VudGllc19VUywgc3RhdGUgPSByZWdpb24sIGNvdW50eSA9IHN1YnJlZ2lvbikKY291bnRpZXNfVVMgPC0gbGVmdF9qb2luKGNvdW50aWVzX1VTLCBjb3VudHkuZmlwcywgYygic3RhdGUiLCAiY291bnR5IikpCmBgYAoKQSBjaG9yb3BsZXRoIG1hcDoKCmBgYHtyfQpnZ3Bsb3QobGVmdF9qb2luKGNvdW50aWVzX1VTLCBhdmdVUywgImZpcHMiKSkgKwogICAgZ2VvbV9wb2x5Z29uKGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZmlsbCA9IGF2Z191bmVtcCwgZ3JvdXAgPSBncm91cCkpICsKICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhuYW1lID0gIlJhdGUiLCBuYS52YWx1ZSA9ICJyZWQiKSArCiAgICB0aGVtZV9tYXAoKSArIGNvb3JkX21hcCgpICsgCiAgICBnZW9tX3BvbHlnb24oYWVzKHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwKSwKICAgICAgICAgICAgICAgICBkYXRhID0gbWFwX2RhdGEoInN0YXRlIiksIGNvbCA9ICJncmV5IiwgZmlsbCA9IE5BKQpgYGAKCkEgdmVyc2lvbiB1c2luZyBgZ2VvbV9tYXBgIGxlYXZlcyBvdXQgU2hhbm5vbiBDb3VudHksIFNEICg0NjExMykgd2hpY2gKZm9yIHNvbWUgcmVhc29uIGlzIG5vdCBpbiB0aGUgTEFVUyBkYXRhLgoKPCEtLSBDaGVjayBob3cgdG8gdXNlIGdlb21fc2Y/IC0tPgoKYGBge3J9CmdncGxvdChhdmdVUywgYWVzKGZpbGwgPSBhdmdfdW5lbXAsIG1hcF9pZCA9IGZpcHMpKSArCiAgICBnZW9tX21hcChtYXAgPSBtdXRhdGUoY291bnRpZXNfVVMsIGlkID0gZmlwcykpICsKICAgIHdpdGgoY291bnRpZXNfVVMsIGV4cGFuZF9saW1pdHMoeCA9IGxvbmcsIHkgPSBsYXQpKSArCiAgICBzY2FsZV9maWxsX3ZpcmlkaXMobmFtZSA9ICJSYXRlIiwgbmEudmFsdWUgPSAicmVkIikgKwogICAgdGhlbWVfbWFwKCkgKyBjb29yZF9tYXAoKQpgYGAKCiAgIAojIyMgR2FwbWluZGVyIENoaWxkaG9vZCBNb3J0YWxpdHkgRGF0YQoKVGhlIGBnYXBtaW5kZXJgIHBhY2thZ2UgcHJvdmlkZXMgYSBzdWJzZXQgb2YgdGhlIGRhdGEgZnJvbSB0aGUKW0dhcG1pbmRlcl0oaHR0cDovL3d3dy5nYXBtaW5kZXIub3JnLykgd2ViIHNpdGUuIEFkZGl0aW9uYWwgZGF0YSBzZXRzCmFyZSBbYXZhaWxhYmxlXShodHRwOi8vd3d3LmdhcG1pbmRlci5vcmcvZGF0YS8pLgoKLSBBIGRhdGEgc2V0IG9uIGNoaWxkaG9vZCBtb3J0YWxpdHkgaXMgYXZhaWxhYmxlIGxvY2FsbHkgYXMgYSBbY3N2CiAgZmlsZV0oaHR0cDovL2hvbWVwYWdlLnN0YXQudWlvd2EuZWR1L35sdWtlL2RhdGEvZ2FwbWluZGVyLXVuZGVyNW1vcnRhbGl0eS5jc3YpCiAgb3IgYW4gW0V4Y2VsCiAgZmlsZV0oaHR0cDovL2hvbWVwYWdlLnN0YXQudWlvd2EuZWR1L35sdWtlL2RhdGEvZ2FwbWluZGVyLXVuZGVyNW1vcnRhbGl0eS54bHN4KS4gVGhlCiAgRXhjZWwgZmlsZSBpcyBhbHNvIGF2YWlsYWJsZSBpbiB0aGUgcHJvamVjdCBkYXRhIGZvbGRlci4KCi0gVGhlIG51bWJlcnMgcmVwcmVzZW50IG51bWJlciBvZiBkZWF0aHMgd2l0aGluIHRoZSBmaXJzdCBmaXZlIHllYXJzCiAgcGVyIDEwMDAgYmlydGhzLgoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+IE1hbnkgcmVzZWFyY2hlcnMgbGlrZSB0byBtYW5hZ2UgdGhlaXIKZGF0YSBpbiBhIHNwcmVhZHNoZWV0LiBCZWluZyBhYmxlIHRvIHJlYWQgc3VjaCBhIHNoZWV0IGRpcmVjdGx5CmdyZWF0bHkgaGVscHMga2VlcGluZyB0aGUgd29ya2Zsb3cgcmVwcm9kdWNpYmxlLgoKTWFueSBzcHJlYWRzaGVldHMgY29udGFpbiBoZWFkZXIsIGZvb3RlcnMsIGFuZCBvdGhlciBhbm5vdGF0aW9ucyB0bwphaWQgYSBodW1hbiB2aWV3ZXIuCgpBcyBsb25nIGFzIHRoZSBkYXRhIGFyZSBpbiBhIHJlY3Rhbmd1bGFyIHJlZ2lvbiBpdCBpcyB1c3VhbGx5IG5vdCBoYXJkCnRvIGV4dHJhY3QgdGhlbSBwcm9ncmFtbWF0aWNhbGx5LiAgPC9kaXY+CgpMb2FkaW5nIHRoZSBkYXRhOgoKYGBge3J9CmxpYnJhcnkocmVhZHhsKQpnY20gPC0gcmVhZF9leGNlbCgiZGF0YS9nYXBtaW5kZXItdW5kZXI1bW9ydGFsaXR5Lnhsc3giKQpuYW1lcyhnY20pWzFdCm5hbWVzKGdjbSlbMV0gPC0gImNvdW50cnkiCmBgYAoKVGhpcyBkYXRhIHNldCBpcyBpbiBfd2lkZV8gZm9ybWF0LgoKQSBfbG9uZ18gdmVyc2lvbiBpcyB1c2VmdWwgZm9yIHdvcmtpbmcgd2l0aCBgZ2dwbG90YC4KCmBgYHtyfQp0Z2NtIDwtIGdhdGhlcihnY20sIHllYXIsIHU1bW9ydCwgLTEpCmhlYWQodGdjbSkKdGdjbSA8LSBtdXRhdGUodGdjbSwgeWVhciA9IGFzLm51bWVyaWMoeWVhcikpCmhlYWQodGdjbSkKYGBgCgoKU29tZSBleHBsb3JhdGlvbnM6CmBgYHtyfQpsaWJyYXJ5KGxhdHRpY2UpCnAgPC0gZ2dwbG90KHRnY20pICsgZ2VvbV9saW5lKGFlcyh5ZWFyLCB1NW1vcnQsIGdyb3VwID0gY291bnRyeSksIGFscGhhID0gMC4zKQpwCnBsb3RseTo6Z2dwbG90bHkocCkKYGBgCgpTb21lIHNlbGVjdGVkIGNvdW50cmllczoKCmBgYHtyfQpjb3VudHJpZXMgPC0gYygiVW5pdGVkIFN0YXRlcyIsICJVbml0ZWQgS2luZ2RvbSIsICJHZXJtYW55IiwgIkNoaW5hIiwgIkVneXB0IikKdGNnbTEgPC0gZmlsdGVyKHRnY20sIGNvdW50cnkgJWluJSBjb3VudHJpZXMpCmdncGxvdCh0Y2dtMSkgKyBnZW9tX2xpbmUoYWVzKHggPSB5ZWFyLCB5ID0gdTVtb3J0LCBjb2xvciA9IGNvdW50cnkpKQpgYGAKCkV4YW1pbmluZyB0aGUgbWlzc2luZyB2YWx1ZXM6CgpgYGB7cn0KdGdjbV9taXNzIDwtIHN1bW1hcml6ZShncm91cF9ieSh0Z2NtLCBjb3VudHJ5KSwgYW55TkEgPSBhbnkoaXMubmEodTVtb3J0KSkpCnRnY21fbWlzcyA8LSBmaWx0ZXIodGdjbV9taXNzLCBhbnlOQSkkY291bnRyeQpwIDwtIGdncGxvdChmaWx0ZXIodGdjbSwgY291bnRyeSAlaW4lIHRnY21fbWlzcykpICsKICAgIGdlb21fbGluZShhZXMoeCA9IHllYXIsIHkgPSB1NW1vcnQsIGdyb3VwID0gY291bnRyeSksIG5hLnJtID0gVFJVRSkKcApwbG90bHk6OmdncGxvdGx5KHApCmBgYAo=